Other Debugging Techniques
Debug Manually with JDB
First, compile the file tests/debug/while.scala
:
$ scalac tests/debug/while.scala
Second, run the compiled class with debugging enabled (suppose the main class is Test
):
$ scala -d Test
Third, start JDB:
$ jdb -attach 5005 -sourcepath tests/debug/
You can run help
for commands that supported by JDB.
Debug Automatically with Expect
1. Annotate the source code with debug information.
Following file (tests/debug/while.scala
) is an example of annotated source code:
object Test {
def main(args: Array[String]): Unit = {
var a = 1 + 2
a = a + 3
a = 4 + 5 // [break] [step: while]
while (a * 8 < 100) { // [step: a += 1]
a += 1 // [step: while] [cont: print]
}
print(a) // [break] [cont]
}
}
The debugging information is annotated as comments to the code in brackets:
val x = f(3) // [break] [next: line=5]
val y = 5
- A JDB command must be wrapped in brackets, like
[step]
. All JDB commands can be used. - To check output of JDB for a command, use
[cmd: expect]
. - If
expect
is wrapped in double quotes, regex is supported. - Break commands are collected and set globally.
- Other commands will be send to jdb in the order they appear in the source file
Note that JDB uses line number starts from 1.
2. Generate Expect File
Now we can run the following command to generate an expect file:
compiler/test/debug/Gen tests/debug/while.scala > robot
3. Run the Test
First, compile the file tests/debug/while.scala
:
$ scalac tests/debug/while.scala
Second, run the compiled class with debugging enabled:
$ scala -d Test
Finally, run the expect script:
expect robot
Other tips
Show for human readable output
Many objects in the compiler have a show
method available on them via implicit rich wrapper:
println(tree.show)
This will output every single tree passing through the typer (or wherever else you inject it) in a human readable form. Try calling show
on anything you want to be human-readable, and chances are it will be possible to do so.
How to disable color
Note that the show
command above outputs the code in color. This is achieved by injecting special characters into the strings which terminals interpret as commands to change color of the output. This however may not be what you want, e.g. if you want to zero-in on a particular tree:
if (tree.show == """println("Hello World")""")
println(s"${tree.show}\n${pt.show}\n${tree.uniqueId}\n===\n")
The intention above is to output an extended debug info on a tree that matches a particular human-readable representation. However, because of the color characters, the comparison will fail.
To disable color output from show
, run scalac
as follows:
scalac -color:never ../issues/Playground.scala
Reporting as a non-intrusive println
Consider you want to debug the tree
that goes into assertPositioned(tree)
in the typed
method. You can do:
println(tree.show)
assertPositioned(tree)
But you can also do:
assertPositioned(tree.reporting(s"Tree is: $result"))
extension (a: A) def reporting(f: WrappedResult[T] ?=> String, p: Printer = Printers.default): A
is defined on all types. The function f
can be written without the argument since it is a context function. The result
variable is a part of the WrapperResult
– a tiny framework powering the reporting
function. Basically, whenever you are using reporting
on an object A
, you can use the result: A
variable from this function and it will be equal to the object you are calling reporting
on.
Printing out trees after phases
To print out the trees you are compiling after the FrontEnd (scanner, parser, namer, typer) phases:
scalac -Xprint:typer ../issues/Playground.scala
To print out the trees after Frontend and CollectSuperCalls phases:
scalac -Xprint:typer,collectSuperCalls ../issues/Playground.scala
To print out the trees after all phases:
scalac -Xprint:all ../issues/Playground.scala
To find out the list of all the phases and their names, check out this line in Compiler.scala
. Each Phase
object has phaseName
defined on it, this is the phase name.
Printing out stack traces of compile time errors
You can use the flag -Ydebug-error
to get the stack trace of all the compile-time errors. Consider the following file:
object Foo
object Foo
Clearly we cannot define an object Foo
twice. Now compile it as follows: scalac -Ydebug-error ../issues/Playground.scala
(use whatever path you saved it under). The result will be as follows:
-- Error: ../issues/Playground.scala:2:0 ---------------------------------------
2 |object Foo
|^
|object Foo has already been compiled once during this run
java.lang.Thread.getStackTrace(Thread.java:1552)
dotty.tools.dotc.reporting.Reporting.error(Reporter.scala:139)
dotty.tools.dotc.core.Contexts$Context.error(Contexts.scala:71)
dotty.tools.dotc.typer.Namer.errorName$2(Namer.scala:300)
dotty.tools.dotc.typer.Namer.checkNoConflict$1(Namer.scala:306)
dotty.tools.dotc.typer.Namer.createSymbol(Namer.scala:353)
dotty.tools.dotc.typer.Namer.recur$1(Namer.scala:490)
dotty.tools.dotc.typer.Namer.recur$3$$anonfun$2(Namer.scala:495)
...
So, the error happened in the Namer's checkNoConflict
method (after which all the stack frames represent the mechanics of issuing an error, not an intent that produced the error in the first place).
Configuring the printer output
Printing from the show
and -Xprint
is done from the Printers framework (discussed in more details below). The following settings influence the output of the printers:
val printLines = BooleanSetting("-print-lines" , "Show source code line numbers.") withAbbreviation "--print-lines"
val uniqid = BooleanSetting("-uniqid" , "Uniquely tag all identifiers in debugging output.") withAbbreviation "--unique-id"
val XprintInline = BooleanSetting("-Xprint-inline" , "Show where inlined code comes from")
val XprintTypes = BooleanSetting("-Xprint-types" , "Print tree types (debugging option).")
val Ydebug = BooleanSetting("-Ydebug" , "Increase the quantity of debugging output.")
val YdebugFlags = BooleanSetting("-Ydebug-flags" , "Print all flags of definitions")
val YdebugMissingRefs = BooleanSetting("-Ydebug-missing-refs", "Print a stacktrace when a required symbol is missing")
val YdebugNames = BooleanSetting("-Ydebug-names" , "Show internal representation of names")
val YdebugPos = BooleanSetting("-Ydebug-pos" , "Show full source positions including spans")
val YdebugTrace = BooleanSetting("-Ydebug-trace" , "Trace core operations")
val YdebugTreeWithId = IntSetting ("-Ydebug-tree-with-id", "Print the stack trace when the tree with the given id is created", Int.MinValue)
val YprintDebug = BooleanSetting("-Yprint-debug" , "when printing trees, print some extra information useful for debugging.")
val YprintDebugOwners = BooleanSetting("-Yprint-debug-owners", "when printing trees, print owners of definitions.")
val YprintPos = BooleanSetting("-Yprint-pos" , "show tree positions.")
val YprintPosSyms = BooleanSetting("-Yprint-pos-syms" , "show symbol definitions positions.")
val YprintSyms = BooleanSetting("-Yprint-syms" , "when printing trees print info in symbols instead of corresponding info in trees.")
val YshowTreeIds = BooleanSetting("-Yshow-tree-ids" , "Uniquely tag all tree nodes in debugging output.")
val YshowVarBounds = BooleanSetting("-Yshow-var-bounds" , "Print type variables with their bounds")
val YtestPickler = BooleanSetting("-Ytest-pickler" , "self-test for pickling functionality; should be used with -Ystop-after:pickler")
They are defined in ScalaSettings.scala. E.g. YprintPos
is defined as:
val YprintPos: Setting[Boolean] = BooleanSetting("-Yprint-pos", "show tree positions.")
And is to be used as:
scalac -Yprint-pos ../issues/Playground.scala
If used, all the trees output with show
or via -Xprint:typer
will also have positions attached to them, e.g.:
package <empty>@<Playground.scala:1> {
module object Playground {
def main(
args:
Array@<Playground.scala:2>[String@<Playground.scala:2>]@<
Playground.scala:2
>
@<Playground.scala:2>
) =
{
println@<Playground.scala:3>("Hello World"@<Playground.scala:3>)@<
Playground.scala:3
>
}@<Playground.scala:2>
@<Playground.scala:2>
}@<Playground.scala:1>
}@<Playground.scala:1>
<empty>@<Playground.scala:1>
Figuring out an object creation site
Via ID
Every Positioned (a parent class of Tree
) object has a uniqueId
field. It is an integer that is unique for that tree and doesn't change from compile run to compile run. You can output these IDs from any printer (such as the ones used by .show
and -Xprint
) via -Yshow-tree-ids
flag, e.g.:
scalac -Xprint:typer -Yshow-tree-ids ../issues/Playground.scala
Gives:
package <empty>#1047 {
final lazy module val Playground: Playground$#1049 =
new Playground$#1049#1050#1051()#1052
#1053
final module class Playground$() extends Object#1090#1091#1092()#1093, _root_#
1061
.scala#1062.Serializable#1063 { this: Playground#1054.type#1055 =>
def main(args: Array#1028[String#1033]#1034#1038): Unit#1039 =
{
println#1094("Hello World"#1041)#1095
}#1096
#1097
}#1099
}#1100
You can then use these IDs to locate the creation site of a given tree using that ID via -Ydebug-tree-with-id
, e.g.:
scalac -Ydebug-tree-with-id 1049 ../issues/Playground.scala
When the tree with the correspond id is allocated, the following prompt will appear:
Debug tree (id=1049) creation
Ident(Playground$)
a)bort, s)tack, r)esume
If you input s
, you will get a stack trace like this:
java.lang.Throwable
at dotty.tools.dotc.reporting.Reporter$.loop$1(Reporter.scala:55)
at dotty.tools.dotc.reporting.Reporter$.displayPrompt(Reporter.scala:63)
at dotty.tools.dotc.ast.Positioned.printTrace$1(Positioned.scala:32)
at dotty.tools.dotc.ast.Positioned.uniqueId_$eq(Positioned.scala:34)
at dotty.tools.dotc.ast.Positioned.<init>(Positioned.scala:45)