From 586c40b6f6931a21cb35eaac4185c74f3603ed26 Mon Sep 17 00:00:00 2001 From: Lucas Nouguier Date: Mon, 7 Aug 2023 10:41:33 +0200 Subject: [PATCH] update readme v1 --- README.md | 99 ++++++++++++++++--- doc/evaluation-provider.svg | 1 + doc/pre-evaluation-activity.svg | 1 + .../dotty/tools/dotc/ExpressionReporter.scala | 2 +- 4 files changed, 88 insertions(+), 15 deletions(-) create mode 100644 doc/evaluation-provider.svg create mode 100644 doc/pre-evaluation-activity.svg diff --git a/README.md b/README.md index b3b3f564f..13dd68135 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # scala-debug-adapter + [![build-badge][]][build] [![Maven Central](https://maven-badges.herokuapp.com/maven-central/ch.epfl.scala/sbt-debug-adapter/badge.svg)](https://maven-badges.herokuapp.com/maven-central/ch.epfl.scala/sbt-debug-adapter) @@ -9,11 +10,12 @@ The scala-debug-adapter is a server-side implementation of the [Debug Adapter Pr It is based on and extends the [microsoft/java-debug](https://github.com/microsoft/java-debug) implementation. The project originated in the [Bloop](https://github.com/scalacenter/bloop) repository, it is now released independently so that it can be used in other build tools of the Scala ecosystem. -For instance, the [sbt-debug-adapter](#sbt-debug-adapter) is an sbt plugin that provides sbt with the debug adapter capability. +For instance, the [sbt-debug-adapter](#sbt-debug-adapter) is an sbt plugin that provides sbt with the debug adapter capability. ## Usage You can add the `scala-debug-adapter` as a dependency in your `build.sbt`: + ```scala // build.sbt scalaVersion := "2.12.16", @@ -28,7 +30,7 @@ While it is always the case with Java 9, you probably want to use the `sbt-jdi-t addSbtPlugin("org.scala-debugger" % "sbt-jdi-tools" % "1.1.1") ``` -You can start a debug server by providing your own intance of `Debuggee`, `DebugToolsResolver` and `Logger`. +You can start a debug server by providing your own instance of `Debuggee`, `DebugToolsResolver` and `Logger`. ### The `Debuggee` @@ -37,6 +39,7 @@ The `Debuggee` is a trait that describes the program that we want to debug. It contains the list of modules of the current project, the list of libraries, the list of unmanaged entries and the java runtime. There are a few difference between a module, a library and an unmanaged entry: + - `Module`: We know its exact scala version, and its compiler options. They will be used by the debugger to evaluate expressions in the module. - `Library`: We only know its binary version. @@ -51,11 +54,12 @@ The sbt version of `Debuggee` can be found [here](https://github.com/scalacenter ### The `DebugToolsResolver` -Depending on the Scala versions specified in the `Debuggee`, the debugger will need to resolve some additionnal tools: +Depending on the Scala versions specified in the `Debuggee`, the debugger will need to resolve some additional tools: + - The expression compiler: to compile the Scala expression that the user wants to evaluate -- The step filter: to filter the intermediate steps of execution that are generated by the compiler, among which the mixin-forwarders, the bridges, the getters and setters, the synthetized methods. +- The step filter (renamed to `unpickler`): to filter the intermediate steps of execution that are generated by the compiler, among which the mixin-forwarders, the bridges, the getters and setters, the synthetized methods. -The role of the `DebugToolsResolver` is to resovle the debug tools and their dependencies, and to load them in a class-loader. +The role of the `DebugToolsResolver` is to resolve the debug tools and their dependencies, and to load them in a class-loader. The debugger takes care of reusing those class-loaders as often as possible: it should call the `DebugToolsResolver` only once by Scala version. To implement the `DebugToolsResolver` you can use the Maven coordinates of the required tools in `ch.epfl.scala.debugadapter.BuildInfo`. @@ -74,21 +78,21 @@ val address = new DebugServer.Address() val tools = DebugTools(debuggee, resolver, logger) // create and start the debug server -val server = DebugServer(debugge, tools, logger, address) +val server = DebugServer(debuggee, tools, logger, address) server.start() // return address.uri for the DAP client to connect address.uri ``` -# sbt-debug-adapter +## sbt-debug-adapter plugin The `sbt-debug-adapter` is an sbt plugin compatible with sbt `1.4.0` or greater. It provides the sbt server with the BSP `debugSession/start` endpoint to start a Scala DAP server. The specification of the `debugSession/start` endpoint can be found in the [Bloop documentation](https://scalacenter.github.io/bloop/docs/debugging-reference). -## Usage +### Plugin usage As a global plugin use `~/.sbt/1.0/plugins/plugins.sbt`, otherwise add to your local project (e.g. `project/plugins.sbt`): @@ -105,19 +109,86 @@ To do so you can use the `sbt-jdi-tools` plugin in the meta project (it goes to addSbtPlugin("org.scala-debugger" % "sbt-jdi-tools" % "1.1.1") ``` -# Development +## Development + +### expression-compiler + +The [`expression-compiler`](https://github.com/scalacenter/scala-debug-adapter/tree/main/modules/expression-compiler) is a module used to compile expression from the debug console. It will insert the expression in the source file, compile it, and return the result of the expression. + +To do so, it adds 2 new phases to the Scala compiler: + +- `extract-expression`: extract the expression from the source file in a new class from where we can evaluate it. To keep the context (local variables), we import them in the new class via a `Map` +- `resolve-reflect-eval`: use reflection to resolve some symbols of the expression -## tests +Debugging the `expression-compiler` requires to enable some logs to show the trees generated by the compiler and the expression compiler. To do so, you need to uncomment the following lines: + +- `println(messageAndPos(...))` in `ExpressionReporter.scala` + - For Scala 3.1+: [file](https://github.com/iusildra/scala-debug-adapter/blob/main/modules/expression-compiler/src/main/scala-3.1+/dotty/tools/dotc/ExpressionReporter.scala) + - For Scala 3.0: [file](https://github.com/iusildra/scala-debug-adapter/blob/main/modules/expression-compiler/src/main/scala-3.0/dotty/tools/dotc/ExpressionReporter.scala) +- `// "-Vprint:typer,extract-expression,resolve-reflect-eval"` in [`ExpressionCompilerBridge.scala`](https://github.com/iusildra/scala-debug-adapter/blob/main/modules/expression-compiler/src/main/scala-3/dotty/tools/dotc/ExpressionCompilerBridge.scala) + +### Runtime evaluator + +#### Idea + +The runtime evaluator is a fully reflection-based evaluation mode designed for simple expressions (without implicits, generics, etc.) written in the debug console. It is less precise than the expression compiler (that can evaluate any kind of expression), but it is much faster (since it does not need to compile). Also, because it is reflection-based, we can access runtime types and private members, which is not possible with the expression compiler. For instance: + +```scala +class A { private val x = 64 } +class B extends A { val y = 42 } + +val a: A = new A +val b: A = new B + +// a.x returns 64 +// b.y returns 42 +``` + +#### Constraints + +Because it is less precise than the expression compiler, it might fail while evaluating an expression (and placing the program in an invalid state), or evaluate it incorrectly (bad overload resolution for instance). To overcome this, a validation step is performed before any evaluation and an order of priority has been defined to choose between the expression compiler and the runtime evaluator: + +![evaluation mode priorities](./doc/evaluation-provider.svg) + +#### Implementation + +The evaluator is implemented in a 3-steps process: + +- parse the expression with [scalameta](https://scalameta.org/) to get the tree representing the expression +- validation: traverse the tree and recursively check that the expression and its sub-expressions can be evaluated at runtime (type-checking, polymorphism overloads resolution, type resolution, etc.). Transform the AST from scalameta to a new AST containing all the information needed for evaluation (see [RuntimeTree.scala](https://github.com/scalacenter/scala-debug-adapter/blob/main/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala)) +- evaluation: consume the new AST and evaluate the expression + +The validation AST is separated in 2 main parts: + +- `RuntimeEvaluableTree`: these nodes are valid expressions by themselves (e.g. a literal, a method call, a module, etc.) +- `RuntimeValidationTree`: theses nodes are not a valid expression by themselves (e.g. a class name), but they can be contained within an evaluable node (e.g. static member access) + +The validation is modeled with a [Validation](https://github.com/scalacenter/scala-debug-adapter/blob/main/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/Validation.scala) monad: + +- `Valid`: the expression is valid +- `Recoverable`: the expression is not valid, but we might be able to recover from it (e.g. when validating `foo`, we might not find a local variable `foo`, but we might find a field, a method, etc. that we can use instead) +- `CompilerRecoverable` when information at runtime is not enough to validate the information (e.g. overloads ambiguity at runtime), the expression compiler might be able to validate it +- `Fatal` for errors that cannot be recovered (e.g. parsing error, unexpected exception, etc.). *Abort the whole evaluation process* + +#### Pre evaluation + +Pre-evaluation is a validation mode that first validate an expression, and evaluate it does not have side effects / fail (only a few nodes can be pre-evaluated). It is used to access the runtime type of an expression to get more information about it. ([RuntimePreEvaluationValidator.scala](https://github.com/scalacenter/scala-debug-adapter/blob/main/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala)) + +### tests The [`tests`](https://github.com/scalacenter/scala-debug-adapter/tree/main/modules/tests/src/test/scala/ch/epfl/scala/debugadapter) module contains the tests of the Scala Debug Server. It uses the [TestingDebugClient](https://github.com/scalacenter/scala-debug-adapter/blob/main/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingDebugClient.scala), a minimal debug client that is used to communicate with the debug server via a socket. -# References +### Show logs + +To print the logs during the tests, you must change the logger in `DebugTestSuite#getDebugServer` and `DebugTestSuite#startDebugServer` from `NoopLogger` to `PrintLogger` + +## References -- [Bloop Debugging Referece](https://scalacenter.github.io/bloop/docs/debugging-reference) +- [Bloop Debugging Reference](https://scalacenter.github.io/bloop/docs/debugging-reference) - [Microsoft DAP for Java](https://github.com/microsoft/vscode-java-debug) - [DebugAdapterProvider](https://github.com/build-server-protocol/build-server-protocol/issues/145) -# History +## History -- [Origial project discussion](https://github.com/scalameta/metals-feature-requests/issues/168) +- [Original project discussion](https://github.com/scalameta/metals-feature-requests/issues/168) diff --git a/doc/evaluation-provider.svg b/doc/evaluation-provider.svg new file mode 100644 index 000000000..56ccc1c85 --- /dev/null +++ b/doc/evaluation-provider.svg @@ -0,0 +1 @@ +expressionvalidate expressionparsing failed orvalidation failedwith Fatal erroryesnoFailurevalidation failedwith non-Fatal erroryesnocompiler evaluationvalidation treehas methodsnoyesruntime evaluationcompiler evaluationruntime evaluationnoevaluation successfulyes \ No newline at end of file diff --git a/doc/pre-evaluation-activity.svg b/doc/pre-evaluation-activity.svg new file mode 100644 index 000000000..ea21b37ac --- /dev/null +++ b/doc/pre-evaluation-activity.svg @@ -0,0 +1 @@ +validate sub-expressionevaluate sub-expressionyesis pre-evaluable?nohas sub-expression? \ No newline at end of file diff --git a/modules/expression-compiler/src/main/scala-3.0/dotty/tools/dotc/ExpressionReporter.scala b/modules/expression-compiler/src/main/scala-3.0/dotty/tools/dotc/ExpressionReporter.scala index 861c1a363..650b638d7 100644 --- a/modules/expression-compiler/src/main/scala-3.0/dotty/tools/dotc/ExpressionReporter.scala +++ b/modules/expression-compiler/src/main/scala-3.0/dotty/tools/dotc/ExpressionReporter.scala @@ -6,7 +6,7 @@ import dotty.tools.dotc.reporting.Diagnostic class ExpressionReporter(reportError: String => Unit) extends AbstractReporter: override def doReport(dia: Diagnostic)(using Context): Unit = - // println(messageAndPos(dia)) + // println(messageAndPos(dia.msg, dia.pos, diagnosticLevel(dia))) dia match case error: Diagnostic.Error => val newPos = error.pos.source.positionInUltimateSource(error.pos)