├── .git-blame-ignore-revs ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── dependency-graph.yml │ ├── release-expression-compiler.yml │ └── release-full.yml ├── .gitignore ├── .gitmodules ├── .scalafmt.conf ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin └── scalafmt ├── build.sbt ├── git-hook.sbt ├── modules ├── core │ └── src │ │ └── main │ │ └── scala │ │ └── ch │ │ └── epfl │ │ └── scala │ │ └── debugadapter │ │ ├── CancelableFuture.scala │ │ ├── ClassEntry.scala │ │ ├── ClassPathEntry.scala │ │ ├── ClassSystem.scala │ │ ├── DebugConfig.scala │ │ ├── DebugServer.scala │ │ ├── DebugToolsResolver.scala │ │ ├── Debuggee.scala │ │ ├── DebuggeeListener.scala │ │ ├── JavaRuntime.scala │ │ ├── Logger.scala │ │ ├── ScalaVersion.scala │ │ ├── SourceEntry.scala │ │ ├── internal │ │ ├── ByteCode.scala │ │ ├── ClassEntryLookUp.scala │ │ ├── CompletionsProvider.scala │ │ ├── DebugSession.scala │ │ ├── DebugTools.scala │ │ ├── Errors.scala │ │ ├── EvaluateNameUtilsProvider.scala │ │ ├── EvaluationProvider.scala │ │ ├── HotCodeReplaceProvider.scala │ │ ├── HotCodeReplacer.scala │ │ ├── IO.scala │ │ ├── LoggingAdapter.scala │ │ ├── NameTransformer.scala │ │ ├── ScalaExtension.scala │ │ ├── ScalaLaunchArguments.scala │ │ ├── ScalaProviderContext.scala │ │ ├── Scheduler.scala │ │ ├── SourceEntryLookUp.scala │ │ ├── SourceLookUpProvider.scala │ │ ├── StackTraceProvider.scala │ │ ├── Synchronized.scala │ │ ├── ThrowOrWarn.scala │ │ ├── TimeUtils.scala │ │ ├── VirtualMachineManagerProvider.scala │ │ ├── evaluator │ │ │ ├── DebuggeeInvocationException.scala │ │ │ ├── ExpressionCompiler.scala │ │ │ ├── JdiArray.scala │ │ │ ├── JdiClass.scala │ │ │ ├── JdiClassLoader.scala │ │ │ ├── JdiFrame.scala │ │ │ ├── JdiNull.scala │ │ │ ├── JdiObject.scala │ │ │ ├── JdiString.scala │ │ │ ├── JdiValue.scala │ │ │ ├── MessageLogger.scala │ │ │ ├── PreparedExpression.scala │ │ │ ├── RuntimeEvaluation.scala │ │ │ ├── RuntimeEvaluator.scala │ │ │ ├── RuntimePrimitiveOps.scala │ │ │ ├── RuntimeTree.scala │ │ │ ├── RuntimeValidation.scala │ │ │ ├── Safe.scala │ │ │ ├── ScalaEvaluator.scala │ │ │ ├── Validation.scala │ │ │ └── package.scala │ │ ├── scalasig │ │ │ ├── Decompiler.scala │ │ │ ├── Flags.scala │ │ │ ├── Parser.scala │ │ │ ├── PickleFormat.scala │ │ │ ├── Ref.scala │ │ │ ├── ScalaSig.scala │ │ │ ├── ScalaSigPrinter.scala │ │ │ ├── ScalaSigReader.scala │ │ │ └── TagGroups.scala │ │ └── stacktrace │ │ │ ├── ClassLoadingFilter.scala │ │ │ ├── DecodedMethodBridge.scala │ │ │ ├── JavaMethod.scala │ │ │ ├── JdiExtensions.scala │ │ │ ├── RuntimeStepFilter.scala │ │ │ ├── Scala2Decoder.scala │ │ │ ├── Scala3Decoder.scala │ │ │ ├── Scala3DecoderBridge.scala │ │ │ ├── ScalaDecoder.scala │ │ │ └── StepFilter.scala │ │ └── testing │ │ ├── TestSuiteEvent.scala │ │ ├── TestSuiteEventHandler.scala │ │ ├── TestSuiteSummary.scala │ │ └── TestUtils.scala ├── decoder │ └── src │ │ └── main │ │ └── scala │ │ └── ch │ │ └── epfl │ │ └── scala │ │ └── debugadapter │ │ └── internal │ │ ├── DecodedMethodBridge.scala │ │ └── Scala3DecoderBridge.scala ├── expression-compiler │ └── src │ │ ├── main │ │ ├── scala-2 │ │ │ └── scala │ │ │ │ └── tools │ │ │ │ └── nsc │ │ │ │ ├── ExpressionCompilerBridge.scala │ │ │ │ ├── ExpressionReporter.scala │ │ │ │ └── evaluation │ │ │ │ ├── ExpressionGlobal.scala │ │ │ │ ├── ExtractExpression.scala │ │ │ │ ├── InsertExpression.scala │ │ │ │ ├── JavaEncoding.scala │ │ │ │ └── ResolveReflectEval.scala │ │ ├── scala-3.0 │ │ │ └── dotty │ │ │ │ └── tools │ │ │ │ └── dotc │ │ │ │ ├── ExpressionCompiler.scala │ │ │ │ ├── ExpressionReporter.scala │ │ │ │ └── evaluation │ │ │ │ ├── ExpressionFrontEnd.scala │ │ │ │ └── SymUtils.scala │ │ ├── scala-3.1+ │ │ │ └── dotty │ │ │ │ └── tools │ │ │ │ └── dotc │ │ │ │ ├── ExpressionCompiler.scala │ │ │ │ └── ExpressionReporter.scala │ │ ├── scala-3.1.0-3.3.3 │ │ │ └── dotty │ │ │ │ └── tools │ │ │ │ └── dotc │ │ │ │ └── evaluation │ │ │ │ └── SymUtils.scala │ │ ├── scala-3.3.4+ │ │ │ └── dotty │ │ │ │ └── tools │ │ │ │ └── dotc │ │ │ │ └── evaluation │ │ │ │ └── SymUtils.scala │ │ ├── scala-3.4+ │ │ │ └── dotty │ │ │ │ └── tools │ │ │ │ └── dotc │ │ │ │ ├── ExpressionCompiler.scala │ │ │ │ └── ExpressionReporter.scala │ │ └── scala-3 │ │ │ └── dotty │ │ │ └── tools │ │ │ └── dotc │ │ │ ├── ExpressionCompilerBridge.scala │ │ │ ├── ExpressionContext.scala │ │ │ └── evaluation │ │ │ ├── EvaluationStrategy.scala │ │ │ ├── ExtractExpression.scala │ │ │ ├── InsertExpression.scala │ │ │ ├── JavaEncoding.scala │ │ │ └── ResolveReflectEval.scala │ │ └── test │ │ ├── scala-2 │ │ └── ch │ │ │ └── epfl │ │ │ └── scala │ │ │ └── debugadapter │ │ │ └── ExpressionCompilerDebug.scala │ │ └── scala-3.3 │ │ └── ch │ │ └── epfl │ │ └── scala │ │ └── debugadapter │ │ └── ExpressionCompilerDebug.scala ├── sbt-plugin │ └── src │ │ ├── main │ │ ├── contraband │ │ │ └── dap.contra │ │ ├── java │ │ │ └── ch │ │ │ │ └── epfl │ │ │ │ └── scala │ │ │ │ └── debugadapter │ │ │ │ └── sbtplugin │ │ │ │ ├── AnnotatedFingerscan.java │ │ │ │ └── SubclassFingerscan.java │ │ └── scala │ │ │ └── ch │ │ │ └── epfl │ │ │ └── scala │ │ │ └── debugadapter │ │ │ └── sbtplugin │ │ │ ├── DebugAdapterPlugin.scala │ │ │ └── internal │ │ │ ├── DebugServerThreadPool.scala │ │ │ ├── DebuggeeProcess.scala │ │ │ ├── DebuggeeProcessLogger.scala │ │ │ ├── Error.scala │ │ │ ├── InternalTasks.scala │ │ │ ├── LoggerAdapter.scala │ │ │ ├── SbtDebugToolsResolver.scala │ │ │ ├── SbtDebuggee.scala │ │ │ └── SbtTestSuiteEventHandler.scala │ │ └── sbt-test │ │ └── debug-session │ │ ├── aggregate │ │ ├── build.sbt │ │ ├── project │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ ├── main │ │ │ │ └── scala │ │ │ │ │ └── example │ │ │ │ │ └── Hello.scala │ │ │ └── test │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ └── HelloSpec.scala │ │ └── test │ │ ├── attach │ │ ├── build.sbt │ │ ├── project │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ └── main │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ └── Main.scala │ │ └── test │ │ ├── debug-java │ │ ├── build.sbt │ │ ├── project │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── test │ │ ├── withG │ │ │ └── src │ │ │ │ └── main │ │ │ │ └── java │ │ │ │ └── example │ │ │ │ └── Main.java │ │ ├── withGSpecialized │ │ │ └── src │ │ │ │ └── main │ │ │ │ └── java │ │ │ │ └── example │ │ │ │ └── Main.java │ │ └── withoutG │ │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── example │ │ │ └── Main.java │ │ ├── hot-code-reload │ │ ├── build.sbt │ │ ├── project │ │ │ ├── DebugState.scala │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ └── main │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ ├── A.scala.1 │ │ │ │ ├── A.scala.2 │ │ │ │ └── Main.scala │ │ └── test │ │ ├── integration-test-suite │ │ ├── build.sbt │ │ ├── project │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ ├── it │ │ │ │ └── scala │ │ │ │ │ └── example │ │ │ │ │ └── MySuite.scala │ │ │ └── main │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ └── Hello.scala │ │ └── test │ │ ├── main-class-scala3 │ │ ├── build.sbt │ │ ├── project │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ └── main │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ └── Main.scala │ │ └── test │ │ ├── main-class │ │ ├── build.sbt │ │ ├── project │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ └── main │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ └── Main.scala │ │ └── test │ │ ├── native-lib │ │ ├── build.sbt │ │ ├── project │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ └── main │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ └── Main.scala │ │ └── test │ │ ├── no-expression-compiler │ │ ├── build.sbt │ │ ├── project │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ └── main │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ └── Main.scala │ │ └── test │ │ ├── scala-collection-compat │ │ ├── build.sbt │ │ ├── project │ │ │ ├── build.properties │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ └── main │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ └── Main.scala │ │ └── test │ │ ├── step-into-jdk │ │ ├── build.sbt │ │ ├── project │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ └── main │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ └── Main.scala │ │ └── test │ │ ├── stepfilter-config │ │ ├── build.sbt │ │ ├── project │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ └── main │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ └── Main.scala │ │ └── test │ │ ├── test-main-class │ │ ├── build.sbt │ │ ├── project │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ └── test │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ └── Main.scala │ │ └── test │ │ ├── test-selection-suite │ │ ├── build.sbt │ │ ├── project │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ └── test │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ └── TestSuite.scala │ │ └── test │ │ ├── test-suite │ │ ├── build.sbt │ │ ├── project │ │ │ ├── plugins.sbt │ │ │ └── project │ │ │ │ └── plugins.sbt │ │ ├── src │ │ │ ├── main │ │ │ │ └── scala │ │ │ │ │ └── example │ │ │ │ │ └── Hello.scala │ │ │ └── test │ │ │ │ └── scala │ │ │ │ └── example │ │ │ │ └── MySuite.scala │ │ └── test │ │ └── unmanaged-jars │ │ ├── build.sbt │ │ ├── project │ │ ├── plugins.sbt │ │ └── project │ │ │ └── plugins.sbt │ │ ├── src │ │ └── main │ │ │ └── scala │ │ │ └── example │ │ │ └── Main.scala │ │ └── test └── tests │ ├── input │ └── class-entry │ │ ├── empty-sources.jar │ │ └── empty.jar │ └── src │ ├── main │ └── scala │ │ └── ch │ │ └── epfl │ │ └── scala │ │ └── debugadapter │ │ └── testfmk │ │ ├── CommonFunSuite.scala │ │ ├── DebugStepAssert.scala │ │ ├── DebugTestSuite.scala │ │ ├── DebugUtils.scala │ │ ├── GithubUtils.scala │ │ ├── MockDebuggee.scala │ │ ├── ScalaInstance.scala │ │ ├── TestingContext.scala │ │ ├── TestingDebugClient.scala │ │ ├── TestingDebuggee.scala │ │ ├── TestingLoggers.scala │ │ └── TestingResolver.scala │ └── test │ └── scala │ └── ch │ └── epfl │ └── scala │ └── debugadapter │ ├── DebugServerTests.scala │ ├── HotCodeReplaceTests.scala │ ├── LocalVariableTests.scala │ ├── ScalaDebugTests.scala │ ├── ScalaEvaluationTests.scala │ ├── ScalaStackTraceTests.scala │ ├── SourceBreakpointTests.scala │ ├── StepFilterTests.scala │ └── internal │ ├── ClassEntryLookUpSpec.scala │ ├── ClassEntryLookUpStats.scala │ ├── JavaRuntimeEvaluatorTests.scala │ ├── MetalsClassBreakpointSuite.scala │ ├── MixedEvaluationTests.scala │ ├── RuntimeEvaluatorTests.scala │ ├── RuntimePreEvaluationTests.scala │ ├── RuntimePrimitiveOperationTests.scala │ ├── SourceLookUpProviderSpec.scala │ ├── SourceLookUpProviderStats.scala │ ├── scalasig │ └── DecompilerSuite.scala │ └── stacktrace │ └── Scala2DecoderTests.scala └── project ├── ContrabandConfig.scala ├── Dependencies.scala ├── Developers.scala ├── SemVer.scala ├── build.properties └── plugins.sbt /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Scala Steward: Reformat with scalafmt 3.8.4 2 | 7fd0641d92a10f1cc0d027860aa8c07caada0cdc 3 | 4 | # Scala Steward: Reformat with scalafmt 3.9.0 5 | f597745711467c5e0f4e382086a5bbb7badd1354 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | paths: 7 | - "modules/**" 8 | - "project/**" 9 | - ".scalafmt.conf" 10 | - ".github/**" 11 | - "build.sbt" 12 | - "git-hook.sbt" 13 | 14 | concurrency: 15 | group: ${{ github.ref }} 16 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} 17 | 18 | jobs: 19 | scalafmt: 20 | if: ${{(github.event_name == 'pull_request' && !contains(github.event.pull_request.body, '[skip ci]'))}} 21 | name: Check Formatting 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | - run: ./bin/scalafmt --test 26 | cross-compilation: 27 | if: ${{(github.event_name == 'pull_request' && !contains(github.event.pull_request.body, '[skip ci]'))}} 28 | name: Cross Compilation 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v4 32 | with: 33 | submodules: true 34 | - uses: coursier/setup-action@v1.3.9 35 | with: 36 | apps: sbt 37 | - run: sbt +Test/compile 38 | test: 39 | if: ${{(github.event_name == 'pull_request' && !contains(github.event.pull_request.body, '[skip ci]'))}} 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | include: 44 | - os: ubuntu-latest 45 | jvm: 'adoptium:1.8.0-372' 46 | - os: windows-latest 47 | jvm: 'adoptium:1.11.0.19' 48 | - os: macOS-latest 49 | jvm: 'adoptium:1.17.0.7' 50 | - os: ubuntu-latest 51 | jvm: 'adoptium:1.20.0.1' 52 | name: Unit Tests on ${{ matrix.os }} -- ${{ matrix.jvm }} 53 | runs-on: ${{ matrix.os }} 54 | steps: 55 | - uses: actions/checkout@v4 56 | with: 57 | submodules: true 58 | - uses: coursier/setup-action@v1.3.9 59 | with: 60 | jvm: ${{ matrix.jvm }} 61 | apps: sbt 62 | - name: Unit tests 63 | run: sbt test 64 | shell: bash 65 | scripted-test: 66 | if: ${{(github.event_name == 'pull_request' && !contains(github.event.pull_request.body, '[skip ci]'))}} 67 | strategy: 68 | fail-fast: false 69 | matrix: 70 | include: 71 | - os: windows-latest 72 | jvm: 'adoptium:1.8.0-372' 73 | - os: ubuntu-latest 74 | jvm: 'adoptium:1.11.0.19' 75 | - os: macOS-latest 76 | jvm: 'adoptium:1.17.0.7' 77 | name: Scripted Tests on ${{ matrix.os }} -- ${{ matrix.jvm }} 78 | runs-on: ${{ matrix.os }} 79 | steps: 80 | - uses: actions/checkout@v4 81 | with: 82 | submodules: true 83 | - uses: coursier/setup-action@v1.3.9 84 | with: 85 | jvm: ${{ matrix.jvm }} 86 | apps: sbt 87 | - name: Scripted tests 88 | run: sbt sbtPlugin/scripted 89 | shell: bash 90 | -------------------------------------------------------------------------------- /.github/workflows/dependency-graph.yml: -------------------------------------------------------------------------------- 1 | name: Sbt Dependency Graph 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | update-graph: 8 | name: Update Dependency Graph 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: scalacenter/sbt-dependency-submission@v3 13 | with: 14 | configs-ignore: scala-doc-tool 15 | -------------------------------------------------------------------------------- /.github/workflows/release-expression-compiler.yml: -------------------------------------------------------------------------------- 1 | name: Release Expression Compiler (manual) 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | scala-version: 6 | required: true 7 | 8 | jobs: 9 | test: 10 | strategy: 11 | fail-fast: false 12 | name: Test with Scala ${{ inputs.scala-version }} on ${{ inputs.tag }} 13 | runs-on: ubuntu-latest 14 | env: 15 | SCALA_VERSION: ${{ inputs.scala-version }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | submodules: true 20 | - uses: coursier/setup-action@v1.3.9 21 | with: 22 | jvm: 'temurin:1.17.0.3' 23 | apps: sbt 24 | - name: Unit tests 25 | run: sbt test 26 | shell: bash 27 | publish: 28 | if: startsWith(github.ref, 'refs/tags/v') 29 | runs-on: ubuntu-latest 30 | env: 31 | SCALA_VERSION: ${{ inputs.scala-version }} 32 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 33 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 34 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 35 | steps: 36 | - uses: actions/checkout@v4 37 | with: 38 | submodules: true 39 | - uses: coursier/setup-action@v1.3.9 40 | with: 41 | apps: sbt 42 | jvm: 'adopt:1.8.0-292' 43 | - name: setup GPG secret key 44 | run: echo ${{ secrets.PGP_SECRET }} | base64 --decode | gpg --batch --import 45 | - name: publish task 46 | run: | 47 | set -e 48 | 49 | VERSION='${{ inputs.scala-version }}' 50 | if [[ $VERSION == 2.12.* ]]; then 51 | echo "Using 2.12 publish task" 52 | sbt 'expressionCompiler212/publishSigned;sonatypeBundleRelease' 53 | 54 | elif [[ $VERSION == 2.13.* ]]; then 55 | echo "Using 2.13 publish task" 56 | sbt 'expressionCompiler213/publishSigned;sonatypeBundleRelease' 57 | 58 | elif [[ $VERSION == 3.0.* ]]; then 59 | echo "Using 3.0 publish task" 60 | sbt 'expressionCompiler30/publishSigned;sonatypeBundleRelease' 61 | 62 | elif [[ $VERSION == 3.1.* || $VERSION == 3.2.* || $VERSION == 3.3.* ]]; then 63 | echo "Using 3.1+ publish task" 64 | sbt 'expressionCompiler31Plus/publishSigned;sonatypeBundleRelease' 65 | 66 | elif [[ $VERSION == 3.* ]]; then 67 | echo "Using 3.4+ publish task" 68 | sbt 'expressionCompiler34Plus/publishSigned;sonatypeBundleRelease' 69 | fi 70 | -------------------------------------------------------------------------------- /.github/workflows/release-full.yml: -------------------------------------------------------------------------------- 1 | name: Full Release (manual) 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | java-debug-version: 6 | required: true 7 | description: 'Next version of java-debug-core (https://repo1.maven.org/maven2/ch/epfl/scala/com-microsoft-java-debug-core/)' 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | submodules: true 15 | - uses: coursier/setup-action@v1.3.9 16 | with: 17 | apps: sbt 18 | jvm: 'adopt:1.8.0-292' 19 | - run: sbt ci-release 20 | env: 21 | JAVA_DEBUG_VERSION: ${{ inputs.java-debug-version }} 22 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 23 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 24 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 25 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .bloop/ 3 | .idea/ 4 | .bsp/ 5 | .vscode/ 6 | .metals/ 7 | **/metals.sbt 8 | *.worksheet.sc 9 | *.plantuml -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "modules/java-debug"] 2 | path = modules/java-debug 3 | url = git@github.com:scalacenter/java-debug.git 4 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "3.9.4" 2 | project.git = true 3 | align.preset = none 4 | align.stripMargin = true 5 | docstrings.style = Asterisk 6 | docstrings.wrap = no 7 | assumeStandardLibraryStripMargin = true 8 | lineEndings = unix 9 | runner.dialect = scala3 10 | maxColumn = 120 11 | rewrite.rules = [RedundantBraces] 12 | rewrite.redundantBraces.generalExpressions = true 13 | rewrite.redundantBraces.stringInterpolation = true 14 | rewrite.redundantBraces.defnBodies = none 15 | fileOverride { 16 | "glob:**/modules/decoder/**.scala" { 17 | rewrite.scala3.convertToNewSyntax = yes 18 | rewrite.scala3.removeOptionalBraces = yes 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bin/scalafmt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalacenter/scala-debug-adapter/72fcfb4eb0021a0622ca9c4a70a3165ef4bf2fc2/bin/scalafmt -------------------------------------------------------------------------------- /git-hook.sbt: -------------------------------------------------------------------------------- 1 | import java.nio.file._ 2 | 3 | Global / onLoad := { state => 4 | val prePush = Paths.get(".git", "hooks", "pre-push") 5 | if (!scala.util.Properties.isWin && !Files.exists(prePush)) { 6 | import java.nio.file._ 7 | Files.createDirectories(prePush.getParent) 8 | Files.write( 9 | prePush, 10 | """#!/bin/sh 11 | |set -eux 12 | |bin/scalafmt --diff --diff-branch main 13 | |git diff --exit-code --ignore-submodules=all 14 | |""".stripMargin.getBytes() 15 | ) 16 | prePush.toFile.setExecutable(true) 17 | } 18 | state 19 | } 20 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/CancelableFuture.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | import scala.concurrent.Future 4 | 5 | /** 6 | * As soon as cancel is called, future is supposed to return 7 | */ 8 | trait CancelableFuture[T] { 9 | def future: Future[T] 10 | def cancel(): Unit 11 | } 12 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/ClassEntry.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | trait ClassEntry { 4 | def name: String 5 | def sourceEntries: Seq[SourceEntry] 6 | def classSystems: Seq[ClassSystem] 7 | } 8 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/ClassPathEntry.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | import java.nio.file.Path 4 | import java.net.URL 5 | 6 | sealed trait ClassPathEntry extends ClassEntry { 7 | def absolutePath: Path 8 | 9 | override def classSystems: Seq[ClassSystem] = Seq(classSystem) 10 | 11 | def readBytes(classFile: String): Array[Byte] = classSystem.readBytes(classFile) 12 | 13 | private def classSystem: ClassSystem = if (isJar) ClassJar(absolutePath) else ClassDirectory(absolutePath) 14 | private def isJar: Boolean = absolutePath.toString.endsWith(".jar") 15 | def toURL: URL = absolutePath.toUri.toURL 16 | } 17 | 18 | final case class UnmanagedEntry(absolutePath: Path) extends ClassPathEntry { 19 | override def name: String = absolutePath.toString 20 | override def sourceEntries: Seq[SourceEntry] = Seq.empty 21 | } 22 | 23 | sealed trait ManagedEntry extends ClassPathEntry { 24 | def scalaVersion: Option[ScalaVersion] 25 | def isScala2: Boolean = scalaVersion.exists(_.isScala2) 26 | def isScala3: Boolean = scalaVersion.exists(_.isScala3) 27 | def isJava: Boolean = scalaVersion.isEmpty 28 | } 29 | 30 | final case class Module( 31 | name: String, 32 | scalaVersion: Option[ScalaVersion], 33 | scalacOptions: Seq[String], 34 | absolutePath: Path, 35 | sourceEntries: Seq[SourceEntry] 36 | ) extends ManagedEntry 37 | 38 | final case class Library(artifactId: String, version: String, absolutePath: Path, sourceEntries: Seq[SourceEntry]) 39 | extends ManagedEntry { 40 | override def name: String = artifactId 41 | def scalaVersion: Option[ScalaVersion] = { 42 | if (artifactId == "scala-library") Some(ScalaVersion(version)) 43 | else { 44 | artifactId 45 | .split('_') 46 | .lastOption 47 | .filter(bv => bv.startsWith("2.12") || bv.startsWith("2.13") || bv.startsWith("3")) 48 | .map(ScalaVersion.apply) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/ClassSystem.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | import ch.epfl.scala.debugadapter.internal.IO 4 | 5 | import java.nio.file.FileSystem 6 | import java.nio.file.FileSystems 7 | import java.nio.file.Path 8 | import java.nio.file.Files 9 | import java.net.URI 10 | import scala.collection.mutable 11 | import java.util.Collections 12 | import scala.util.Try 13 | 14 | sealed trait ClassSystem { 15 | def name: String 16 | def within[T](f: (FileSystem, Path) => T): Try[T] 17 | def readBytes(path: String): Array[Byte] = 18 | within { (_, root) => 19 | Files.readAllBytes(root.resolve(path)) 20 | }.get 21 | } 22 | 23 | final case class ClassJar(absolutePath: Path) extends ClassSystem { 24 | def name: String = absolutePath.toString 25 | def within[T](f: (FileSystem, Path) => T): Try[T] = 26 | IO.withinJarFile(absolutePath)(fs => f(fs, fs.getPath("/"))) 27 | } 28 | 29 | final case class ClassDirectory(absolutePath: Path) extends ClassSystem { 30 | def name: String = absolutePath.toString 31 | def within[T](f: (FileSystem, Path) => T): Try[T] = Try { 32 | f(FileSystems.getDefault, absolutePath) 33 | } 34 | } 35 | 36 | final case class JavaRuntimeSystem(classLoader: ClassLoader, javaHome: Path) extends ClassSystem { 37 | def name: String = javaHome.toString 38 | def fileSystem: FileSystem = 39 | JavaRuntimeSystem.getFileSystem(classLoader, javaHome) 40 | 41 | def within[T](f: (FileSystem, Path) => T): Try[T] = Try { 42 | f(fileSystem, fileSystem.getPath("/modules")) 43 | } 44 | } 45 | 46 | object JavaRuntimeSystem { 47 | private val fileSystems: mutable.Map[Path, FileSystem] = mutable.Map() 48 | 49 | def getFileSystem(classLoader: ClassLoader, javaHome: Path): FileSystem = { 50 | val properties = Collections.singletonMap("java.home", javaHome.toString) 51 | fileSystems.getOrElseUpdate( 52 | javaHome, 53 | FileSystems.newFileSystem(URI.create("jrt:/"), properties, classLoader) 54 | ) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/DebugConfig.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | import scala.concurrent.duration.* 4 | 5 | case class StepFiltersConfig( 6 | skipForwardersAndAccessors: Boolean, 7 | skipRuntimeClasses: Boolean, 8 | skipClassLoading: Boolean 9 | ) 10 | 11 | object StepFiltersConfig { 12 | val default = StepFiltersConfig(skipForwardersAndAccessors = true, skipRuntimeClasses = true, skipClassLoading = true) 13 | } 14 | 15 | final case class DebugConfig( 16 | gracePeriod: Duration, 17 | testMode: Boolean, 18 | evaluationMode: DebugConfig.EvaluationMode, 19 | stepFilters: StepFiltersConfig 20 | ) 21 | 22 | object DebugConfig { 23 | def default: DebugConfig = DebugConfig( 24 | 5.seconds, 25 | testMode = false, 26 | evaluationMode = DebugConfig.MixedEvaluation, 27 | stepFilters = StepFiltersConfig.default 28 | ) 29 | 30 | sealed trait EvaluationMode { 31 | def allowScalaEvaluation: Boolean = false 32 | def allowRuntimeEvaluation: Boolean = false 33 | } 34 | 35 | case object ScalaEvaluationOnly extends EvaluationMode { 36 | override def allowScalaEvaluation: Boolean = true 37 | } 38 | case object RuntimeEvaluationOnly extends EvaluationMode { 39 | override def allowRuntimeEvaluation: Boolean = true 40 | } 41 | 42 | case object NoEvaluation extends EvaluationMode 43 | case object MixedEvaluation extends EvaluationMode { 44 | override def allowScalaEvaluation: Boolean = true 45 | override def allowRuntimeEvaluation: Boolean = true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/DebugToolsResolver.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | import java.nio.file.Path 4 | import scala.util.Try 5 | 6 | trait DebugToolsResolver { 7 | def resolveExpressionCompiler(scalaVersion: ScalaVersion): Try[ClassLoader] 8 | def resolveDecoder(scalaVersion: ScalaVersion): Try[Seq[Path]] 9 | } 10 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/Debuggee.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | import java.nio.file.Path 4 | import java.io.File 5 | import java.io.Closeable 6 | 7 | trait Debuggee { 8 | def name: String 9 | def scalaVersion: ScalaVersion 10 | def run(listener: DebuggeeListener): CancelableFuture[Unit] 11 | def modules: Seq[Module] 12 | def libraries: Seq[Library] 13 | def unmanagedEntries: Seq[UnmanagedEntry] 14 | def javaRuntime: Option[JavaRuntime] 15 | def observeClassUpdates(onClassUpdate: Seq[String] => Unit): Closeable 16 | 17 | def managedEntries: Seq[ManagedEntry] = modules ++ libraries 18 | def classPathEntries: Seq[ClassPathEntry] = managedEntries ++ unmanagedEntries 19 | def classPath: Seq[Path] = classPathEntries.map(_.absolutePath) 20 | def classEntries: Seq[ClassEntry] = classPathEntries ++ javaRuntime 21 | def classPathString: String = classPath.mkString(File.pathSeparator) 22 | } 23 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/DebuggeeListener.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | import java.net.InetSocketAddress 4 | import com.microsoft.java.debug.core.protocol.Events.DebugEvent 5 | import ch.epfl.scala.debugadapter.testing.TestSuiteSummary 6 | 7 | final class TestResultEvent private ( 8 | val category: String, 9 | val data: TestSuiteSummary 10 | ) extends DebugEvent("testResult") 11 | object TestResultEvent { 12 | def apply(data: TestSuiteSummary): TestResultEvent = 13 | new TestResultEvent("testResult", data) 14 | } 15 | 16 | trait DebuggeeListener { 17 | def onListening(address: InetSocketAddress): Unit 18 | def out(line: String): Unit 19 | def err(line: String): Unit 20 | def testResult(data: TestSuiteSummary): Unit 21 | } 22 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/JavaRuntime.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | import java.net.URLClassLoader 4 | import java.nio.file.Files 5 | import java.nio.file.Path 6 | import java.nio.file.Paths 7 | 8 | sealed trait JavaRuntime extends ClassEntry { 9 | def javaHome: Path 10 | def name: String = javaHome.getFileName.toString 11 | } 12 | 13 | final case class Java8(javaHome: Path, classJars: Seq[Path], sourceZip: Option[Path]) extends JavaRuntime { 14 | override def sourceEntries: Seq[SourceEntry] = sourceZip.map(SourceJar.apply).toSeq 15 | override def classSystems: Seq[ClassSystem] = classJars.map(ClassJar.apply) 16 | } 17 | 18 | final case class Java9OrAbove(javaHome: Path, fsJar: Path, sourceZip: Option[Path]) extends JavaRuntime { 19 | override def sourceEntries: Seq[SourceEntry] = sourceZip.map(SourceJar.apply).toSeq 20 | override def classSystems: Seq[JavaRuntimeSystem] = { 21 | val classLoader = new URLClassLoader(Array(fsJar.toUri.toURL), null) 22 | Seq(JavaRuntimeSystem(classLoader, javaHome)) 23 | } 24 | } 25 | 26 | object JavaRuntime { 27 | def apply(javaHome: String): Option[JavaRuntime] = 28 | JavaRuntime(Paths.get(javaHome)) 29 | 30 | def apply(javaHome: Path): Option[JavaRuntime] = { 31 | val sources = Seq("src.zip", "lib/src.zip", "../src.zip") 32 | .map(javaHome.resolve) 33 | .find(Files.exists(_)) 34 | java8(javaHome, sources).orElse(java9OrAbove(javaHome, sources)) 35 | } 36 | 37 | private[debugadapter] def java8(javaHome: Path, srcZip: Option[Path]): Option[JavaRuntime] = { 38 | for { 39 | runtimeJar <- Seq("jre/lib/rt.jar", "lib/rt.jar") 40 | .map(javaHome.resolve) 41 | .find(Files.exists(_)) 42 | } yield { 43 | val otherJars = Seq("jre/lib/charsets.jar", "lib/charsets.jar") 44 | .map(javaHome.resolve) 45 | .filter(Files.exists(_)) 46 | Java8(javaHome, Seq(runtimeJar) ++ otherJars, srcZip) 47 | } 48 | } 49 | 50 | private[debugadapter] def java9OrAbove( 51 | javaHome: Path, 52 | srcZip: Option[Path] 53 | ): Option[JavaRuntime] = { 54 | Some("lib/jrt-fs.jar") 55 | .map(javaHome.resolve) 56 | .filter(Files.exists(_)) 57 | .map(Java9OrAbove(javaHome, _, srcZip)) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/Logger.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | trait Logger { 4 | def debug(msg: => String): Unit 5 | def info(msg: => String): Unit 6 | def warn(msg: => String): Unit 7 | def error(msg: => String): Unit 8 | def trace(t: => Throwable): Unit 9 | } 10 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/ScalaVersion.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | case class ScalaVersion(value: String) extends Ordered[ScalaVersion] { 4 | 5 | def isScala2: Boolean = value.startsWith("2") 6 | def isScala3: Boolean = value.startsWith("3") 7 | def isScala212: Boolean = value.startsWith("2.12") 8 | def isScala213: Boolean = value.startsWith("2.13") 9 | def isScala33: Boolean = value.startsWith("3.3") 10 | def isScala34: Boolean = value.startsWith("3.4") 11 | 12 | def parts: (Int, Int, Int) = { 13 | val regex = "(\\d+)\\.(\\d+)\\.(\\d+)(-.+)?".r 14 | regex 15 | .unapplySeq(value) 16 | .collect { case major :: minor :: patch :: tail => 17 | (major.toInt, minor.toInt, patch.toInt) 18 | } 19 | .get 20 | } 21 | 22 | override def compare(that: ScalaVersion): Int = 23 | (parts, that.parts) match { 24 | case ((x, _, _), (y, _, _)) if x != y => x - y 25 | case ((_, x, _), (_, y, _)) if x != y => x - y 26 | case ((_, _, x), (_, _, y)) if x != y => x - y 27 | case _ => 0 28 | } 29 | 30 | def binaryVersion: String = if (isScala3) "3" else if (isScala213) "2.13" else "2.12" 31 | 32 | def isRelease: Boolean = !value.contains("-") 33 | 34 | override def toString: String = value 35 | } 36 | 37 | object ScalaVersion { 38 | val `2.11` = ScalaVersion(value = "2.11.12") 39 | val `2.12` = ScalaVersion(BuildInfo.scala212) 40 | val `2.13` = ScalaVersion(BuildInfo.scala213) 41 | val `3.0` = ScalaVersion(BuildInfo.scala30) 42 | val `3.1+` = ScalaVersion(BuildInfo.scala31Plus) 43 | val `3.4+` = ScalaVersion(BuildInfo.scala34Plus) 44 | val `3.5.0` = ScalaVersion("3.5.0") 45 | } 46 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/SourceEntry.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | import java.nio.file.Path 4 | 5 | sealed trait SourceEntry { 6 | def name: String 7 | } 8 | final case class SourceDirectory(directory: Path) extends SourceEntry { 9 | def name: String = directory.toString 10 | } 11 | final case class SourceJar(jar: Path) extends SourceEntry { 12 | def name: String = jar.toString 13 | } 14 | final case class StandaloneSourceFile(absolutePath: Path, relativePath: String) extends SourceEntry { 15 | def name: String = relativePath.toString 16 | } 17 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/ByteCode.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | object ByteCode { 4 | // return void from method 5 | val RETURN: Byte = 0xb1.toByte 6 | val NEW: Byte = 0xbb.toByte 7 | val ANEWARRAY: Byte = 0xbd.toByte 8 | val MULTIANEWARRAY: Byte = 0xc5.toByte 9 | val LDC: Byte = 0x12.toByte 10 | val INSTANCEOF: Byte = 0xc1.toByte 11 | val CHECKCAST: Byte = 0xc0.toByte 12 | } 13 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/CompletionsProvider.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import com.microsoft.java.debug.core.adapter.ICompletionsProvider 4 | import com.microsoft.java.debug.core.protocol.Types 5 | import com.sun.jdi.StackFrame 6 | 7 | import java.util 8 | 9 | object CompletionsProvider extends ICompletionsProvider { 10 | override def codeComplete( 11 | frame: StackFrame, 12 | snippet: String, 13 | line: Int, 14 | column: Int 15 | ): util.List[Types.CompletionItem] = util.Collections.emptyList() 16 | } 17 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/Errors.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import com.microsoft.java.debug.core.DebugException 4 | import com.microsoft.java.debug.core.adapter.ErrorCode 5 | 6 | object Errors { 7 | def hotCodeReplaceFailure(message: String): DebugException = 8 | userError(s"hot code replace failed: $message", ErrorCode.HCR_FAILURE) 9 | 10 | def runtimeValidationFailure(message: String): DebugException = 11 | userError(s"runtime validation failed: $message", ErrorCode.EVALUATE_FAILURE) 12 | 13 | def frameDecodingFailure(message: String): DebugException = 14 | error(s"decoding frame failed: $message", ErrorCode.STEP_FAILURE) 15 | 16 | def compilationFailure(errors: Seq[String]): DebugException = 17 | userError("compilation failed:\n" + errors.mkString("\n"), ErrorCode.EVALUATION_COMPILE_ERROR) 18 | 19 | def evaluationFailure(message: String): DebugException = 20 | userError(s"evaluation failed: $message", ErrorCode.EVALUATE_FAILURE) 21 | 22 | def frameDecodingFailure(cause: Throwable): DebugException = 23 | error(cause, ErrorCode.STEP_FAILURE) 24 | 25 | private def userError(message: String, code: ErrorCode): DebugException = 26 | new DebugException(message, code.getId, true) 27 | 28 | private def error(message: String, code: ErrorCode): DebugException = 29 | new DebugException(message, code.getId) 30 | 31 | private def error(cause: Throwable, code: ErrorCode): DebugException = 32 | new DebugException(cause, code.getId) 33 | } 34 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/EvaluateNameUtilsProvider.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import com.microsoft.java.debug.core.adapter.variables.IVariableProvider 4 | 5 | private[internal] object VariableProvider extends IVariableProvider { 6 | override def getEvaluateName(name: String, containerName: String, isArrayElement: Boolean): String = 7 | (name, containerName, isArrayElement) match { 8 | case (null, _, _) => null 9 | case (_, null, true) => null 10 | case (name, containerName, true) => s"$containerName($name)" 11 | case (name, null, _) => name 12 | case _ => s"$containerName.$name" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/IO.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import java.net.URI 4 | import java.nio.file.FileSystem 5 | import java.nio.file.FileSystems 6 | import java.nio.file.Path 7 | import java.util 8 | import scala.util.Try 9 | import scala.util.control.NonFatal 10 | import scala.util.Failure 11 | import scala.util.Success 12 | 13 | private[debugadapter] object IO { 14 | type CloseFileSystem = Boolean 15 | 16 | def withinJarFile[T](absolutePath: Path)(f: FileSystem => T): Try[T] = 17 | getJarFileSystem(absolutePath).map { case (fs, shouldClose) => 18 | try f(fs) 19 | finally if (shouldClose) fs.close() 20 | } 21 | 22 | def getJarFileSystem(absolutePath: Path): Try[(FileSystem, CloseFileSystem)] = try { 23 | val uri = URI.create(s"jar:${absolutePath.toUri}") 24 | val fileSystem = 25 | try ((FileSystems.getFileSystem(uri), false)) 26 | catch { 27 | case NonFatal(_) => 28 | (FileSystems.newFileSystem(uri, new util.HashMap[String, Any]), true) 29 | } 30 | Success(fileSystem) 31 | } catch { 32 | case NonFatal(exception) => Failure(exception) 33 | case zipError: util.zip.ZipError => Failure(zipError) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/LoggingAdapter.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import com.microsoft.java.debug.core.{Configuration, LoggerFactory} 4 | 5 | import java.util.logging.{Logger => JLogger, _} 6 | 7 | private[debugadapter] class LoggingAdapter( 8 | logger: ch.epfl.scala.debugadapter.Logger 9 | ) extends Handler { 10 | 11 | /** 12 | * DAP server tends to send a lot of SocketClosed exceptions when the Debuggee process has exited. This helps us filter those logs 13 | */ 14 | @volatile private var closingSession = false 15 | 16 | override def publish(record: LogRecord): Unit = { 17 | val message = record.getMessage 18 | record.getLevel match { 19 | case Level.INFO | Level.CONFIG => logger.info(message) 20 | case Level.WARNING => logger.warn(message) 21 | case Level.SEVERE => 22 | if (isExpectedDuringCancellation(message) || isIgnoredError(message)) logger.debug(message) 23 | else { 24 | logger.error(message) 25 | Option(record.getThrown).foreach(logger.trace(_)) 26 | } 27 | case _ => logger.debug(message) 28 | } 29 | } 30 | 31 | def onClosingSession(): Unit = { 32 | closingSession = true 33 | } 34 | 35 | def factory: LoggerFactory = { name => 36 | val logger = JLogger.getLogger(name) 37 | logger.getHandlers.foreach(logger.removeHandler) 38 | logger.setUseParentHandlers(false) 39 | if (name == Configuration.LOGGER_NAME) logger.addHandler(this) 40 | logger 41 | } 42 | 43 | override def flush(): Unit = () 44 | 45 | override def close(): Unit = () 46 | 47 | private final val socketClosed = "java.net.SocketException: Socket closed" 48 | 49 | private def isExpectedDuringCancellation(message: String): Boolean = { 50 | message.endsWith(socketClosed) && closingSession 51 | } 52 | 53 | private final val recordingWhenVmDisconnected = 54 | "Exception on recording event: com.sun.jdi.VMDisconnectedException" 55 | 56 | private def isIgnoredError(message: String): Boolean = { 57 | message.startsWith(recordingWhenVmDisconnected) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/ScalaExtension.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import ch.epfl.scala.debugadapter.Logger 4 | import scala.util.Success 5 | import scala.util.Failure 6 | import scala.util.Try 7 | import java.util.concurrent.CompletableFuture 8 | 9 | private[debugadapter] object ScalaExtension { 10 | 11 | implicit class TryExtension[T](x: Try[T]) { 12 | def warnFailure(logger: Logger, message: String): Option[T] = x match { 13 | case Success(value) => Some(value) 14 | case Failure(e) => 15 | logger.warn(s"$message: ${e.getClass.getSimpleName} ${e.getMessage}") 16 | None 17 | } 18 | 19 | def toCompletableFuture: CompletableFuture[T] = { 20 | val future = new CompletableFuture[T]() 21 | x match { 22 | case Success(value) => future.complete(value) 23 | case Failure(e) => future.completeExceptionally(e) 24 | } 25 | future 26 | } 27 | } 28 | 29 | implicit class OptionExtension[T](opt: Option[T]) { 30 | def toTry(message: String): Try[T] = { 31 | opt match { 32 | case Some(value) => Success(value) 33 | case None => Failure(new Exception(message)) 34 | } 35 | } 36 | } 37 | 38 | implicit class TrySeq[A](seq: Seq[Try[A]]) { 39 | def traverse: Try[Seq[A]] = { 40 | seq.foldRight(Try(Seq.empty[A])) { (safeHead, safeTail) => 41 | safeTail.flatMap(tail => safeHead.map(head => head +: tail)) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/ScalaLaunchArguments.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal; 2 | 3 | import ch.epfl.scala.debugadapter.StepFiltersConfig 4 | 5 | case class ScalaLaunchArguments( 6 | noDebug: Boolean, 7 | scalaStepFilters: StepFiltersConfig 8 | ) 9 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/ScalaProviderContext.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import ch.epfl.scala.debugadapter.DebugConfig 4 | import ch.epfl.scala.debugadapter.Debuggee 5 | import ch.epfl.scala.debugadapter.Logger 6 | import com.microsoft.java.debug.core.DebugSettings 7 | import com.microsoft.java.debug.core.adapter.ICompletionsProvider 8 | import com.microsoft.java.debug.core.adapter.IEvaluationProvider 9 | import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider 10 | import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider 11 | import com.microsoft.java.debug.core.adapter.IStackTraceProvider 12 | import com.microsoft.java.debug.core.adapter.IVirtualMachineManagerProvider 13 | import com.microsoft.java.debug.core.adapter.ProviderContext 14 | import com.microsoft.java.debug.core.adapter.variables.IVariableProvider 15 | 16 | private[debugadapter] class ScalaProviderContext private ( 17 | debuggee: Debuggee, 18 | logger: Logger 19 | ) extends ProviderContext { 20 | def configure(tools: DebugTools, config: DebugConfig): Unit = { 21 | registerProvider(classOf[ISourceLookUpProvider], tools.sourceLookUp) 22 | registerProvider(classOf[IEvaluationProvider], EvaluationProvider(debuggee, tools, logger, config)) 23 | registerProvider( 24 | classOf[IStackTraceProvider], 25 | StackTraceProvider(debuggee, tools, logger, config) 26 | ) 27 | } 28 | } 29 | 30 | private[debugadapter] object ScalaProviderContext { 31 | def apply( 32 | debuggee: Debuggee, 33 | logger: Logger, 34 | config: DebugConfig 35 | ): ScalaProviderContext = { 36 | 37 | /** 38 | * Since Scala 2.13, object fields are represented by static fields in JVM byte code. 39 | * See https://github.com/scala/scala/pull/7270 40 | */ 41 | DebugSettings.getCurrent.showStaticVariables = true 42 | 43 | val context = new ScalaProviderContext(debuggee, logger) 44 | val hotCodeReplaceProvider = HotCodeReplaceProvider(debuggee, logger, config.testMode) 45 | // The BreakpointRequestHandler resolves the IHotCodeReplaceProvider in its constructor 46 | context.registerProvider(classOf[IHotCodeReplaceProvider], hotCodeReplaceProvider) 47 | context.registerProvider(classOf[ICompletionsProvider], CompletionsProvider) 48 | context.registerProvider(classOf[IVirtualMachineManagerProvider], VirtualMachineManagerProvider) 49 | context.registerProvider(classOf[IVariableProvider], VariableProvider) 50 | context 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/Scheduler.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import java.util.concurrent.TimeoutException 4 | import java.util.{Timer, TimerTask} 5 | import scala.concurrent.Promise 6 | import scala.concurrent.duration.Duration 7 | 8 | private[debugadapter] object Scheduler { 9 | private val timer = new Timer("DAP Timeout Scheduler", true) 10 | 11 | def timeout[T](promise: Promise[T], duration: Duration): Promise[T] = { 12 | val task = new TimerTask { 13 | def run(): Unit = 14 | promise.tryFailure( 15 | new TimeoutException(s"Operation timed out after $duration") 16 | ) 17 | } 18 | timer.schedule(task, duration.toMillis) 19 | promise 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/SourceEntryLookUp.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import ch.epfl.scala.debugadapter.SourceEntry 4 | import ch.epfl.scala.debugadapter.SourceJar 5 | import ch.epfl.scala.debugadapter.SourceDirectory 6 | import ch.epfl.scala.debugadapter.StandaloneSourceFile 7 | import java.nio.file.FileSystems 8 | import java.nio.file.FileSystem 9 | import java.nio.file.Path 10 | import java.nio.file.Files 11 | import scala.jdk.CollectionConverters.* 12 | import java.net.URI 13 | import ch.epfl.scala.debugadapter.Logger 14 | import ch.epfl.scala.debugadapter.internal.ScalaExtension.* 15 | import scala.util.control.NonFatal 16 | 17 | private case class SourceFile( 18 | entry: SourceEntry, 19 | relativePath: String, 20 | uri: URI 21 | ) { 22 | def fileName: String = relativePath.split('/').last 23 | def folderPath: String = relativePath.stripSuffix(s"/$fileName") 24 | } 25 | 26 | private case class SourceEntryLookUp( 27 | entry: SourceEntry, 28 | sourceFiles: Seq[SourceFile], 29 | fileSystem: FileSystem, 30 | root: Path, 31 | shouldCloseFileSystem: Boolean = false 32 | ) { 33 | def close(): Unit = 34 | try 35 | if (shouldCloseFileSystem) { 36 | fileSystem.close() 37 | } 38 | catch { 39 | case NonFatal(_) => () 40 | } 41 | } 42 | 43 | private object SourceEntryLookUp { 44 | def apply(entry: SourceEntry, logger: Logger): Option[SourceEntryLookUp] = { 45 | entry match { 46 | case SourceJar(jar) => 47 | IO.getJarFileSystem(jar) 48 | .map { case (fs, shouldClose) => 49 | val root = fs.getPath("/") 50 | val sourceFiles = getAllSourceFiles(entry, fs, root).toVector 51 | SourceEntryLookUp(entry, sourceFiles, fs, root, shouldClose) 52 | } 53 | .warnFailure(logger, s"Cannot list the source files in ${entry.name}") 54 | case SourceDirectory(directory) => 55 | val fs = FileSystems.getDefault 56 | val sourceFiles = getAllSourceFiles(entry, fs, directory).toVector 57 | Some(SourceEntryLookUp(entry, sourceFiles, fs, directory)) 58 | case StandaloneSourceFile(absolutePath, relativePath) => 59 | val fs = FileSystems.getDefault 60 | val sourceFile = SourceFile(entry, relativePath, absolutePath.toUri) 61 | val root = fs.getPath(sourceFile.folderPath) 62 | Some(SourceEntryLookUp(entry, Seq(sourceFile), FileSystems.getDefault, root)) 63 | } 64 | } 65 | 66 | private def getAllSourceFiles( 67 | entry: SourceEntry, 68 | fileSystem: FileSystem, 69 | root: Path 70 | ): Iterator[SourceFile] = { 71 | if (Files.exists(root)) { 72 | val sourceMatcher = fileSystem.getPathMatcher("glob:**.{scala,java}") 73 | Files 74 | .walk(root: Path) 75 | .filter(sourceMatcher.matches) 76 | .iterator 77 | .asScala 78 | .map { path => 79 | val relativePath = root.relativize(path).toString.replace('\\', '/') 80 | SourceFile(entry, relativePath, path.toUri) 81 | } 82 | } else Iterator.empty 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/StackTraceProvider.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import ch.epfl.scala.debugadapter.Debuggee 4 | import ch.epfl.scala.debugadapter.Logger 5 | import ch.epfl.scala.debugadapter.internal.stacktrace.* 6 | import com.microsoft.java.debug.core.adapter.{StackTraceProvider => JavaStackTraceProvider} 7 | import com.microsoft.java.debug.core.protocol.Requests.StepFilters 8 | import com.sun.jdi.Location 9 | import com.sun.jdi.Method 10 | import com.microsoft.java.debug.core.adapter.stacktrace.DecodedMethod 11 | import ch.epfl.scala.debugadapter.DebugConfig 12 | 13 | class StackTraceProvider( 14 | stepFilters: Seq[StepFilter], 15 | protected val logger: Logger, 16 | protected val testMode: Boolean 17 | ) extends JavaStackTraceProvider 18 | with ThrowOrWarn { 19 | val decoder = stepFilters.collectFirst { case u: ScalaDecoder => u } 20 | def reload(): Unit = decoder.foreach(_.reload()) 21 | 22 | override def decode(method: Method): DecodedMethod = 23 | decoder.map(_.decode(method)).getOrElse(JavaMethod(method, isGenerated = false)) 24 | 25 | override def skipOver(method: Method, filters: StepFilters): Boolean = { 26 | try { 27 | val skipOver = super.skipOver(method, filters) || stepFilters.exists(_.skipOver(method)) 28 | if (skipOver) logger.debug(s"Skipping over $method") 29 | skipOver 30 | } catch { 31 | case cause: Throwable => 32 | throwOrWarn(s"Failed to determine if $method should be skipped over: ${cause.getMessage}") 33 | false 34 | } 35 | } 36 | 37 | override def skipOut(upperLocation: Location, method: Method): Boolean = { 38 | try { 39 | val skipOut = 40 | super.skipOut(upperLocation, method) || 41 | stepFilters.exists(_.skipOut(upperLocation, method)) 42 | if (skipOut) logger.debug(s"Skipping out $method") 43 | skipOut 44 | } catch { 45 | case cause: Throwable => 46 | throwOrWarn(s"Failed to determine if $method should be skipped out: ${cause.getMessage}") 47 | false 48 | } 49 | } 50 | } 51 | 52 | object StackTraceProvider { 53 | private def stepFiltersProvider( 54 | debuggee: Debuggee, 55 | tools: DebugTools, 56 | logger: Logger, 57 | config: DebugConfig 58 | ): Seq[StepFilter] = { 59 | var list = List.empty[StepFilter] 60 | if (config.stepFilters.skipClassLoading) list = ClassLoadingFilter +: list 61 | if (config.stepFilters.skipRuntimeClasses) list = RuntimeStepFilter(debuggee.scalaVersion) +: list 62 | if (config.stepFilters.skipForwardersAndAccessors) 63 | list = TimeUtils.logTime(logger, "Initialized Scala 3 decoder") { 64 | ScalaDecoder(debuggee, tools, logger, config.testMode) 65 | } +: list 66 | list 67 | } 68 | 69 | def apply( 70 | debuggee: Debuggee, 71 | tools: DebugTools, 72 | logger: Logger, 73 | config: DebugConfig 74 | ): StackTraceProvider = { 75 | new StackTraceProvider( 76 | stepFiltersProvider(debuggee, tools, logger, config), 77 | logger, 78 | config.testMode 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/Synchronized.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | private[debugadapter] final class Synchronized[A](var value: A) { 4 | def transform(f: A => A): Unit = 5 | synchronized { 6 | value = f(value) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/ThrowOrWarn.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import ch.epfl.scala.debugadapter.Logger 4 | import com.microsoft.java.debug.core.DebugException 5 | 6 | trait ThrowOrWarn { 7 | protected val testMode: Boolean 8 | protected val logger: Logger 9 | 10 | protected def throwOrWarn(msg: String): Unit = 11 | if (testMode) throw new DebugException(msg) 12 | else logger.warn(msg) 13 | 14 | protected def throwOrWarn(e: Throwable): Unit = 15 | if (testMode) throw new DebugException(e) 16 | else logger.warn(s"Unexpected exception ${e.getClass.getName}: ${e.getMessage}") 17 | 18 | protected def throwOrWarn(message: String, e: Throwable) = 19 | if (testMode) throw new DebugException(e) 20 | else logger.warn(s"$message because of ${e.getClass.getSimpleName}: ${e.getMessage}") 21 | } 22 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/TimeUtils.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import scala.concurrent.duration.Duration 4 | import java.util.concurrent.TimeUnit 5 | import ch.epfl.scala.debugadapter.Logger 6 | 7 | private[debugadapter] object TimeUtils { 8 | def logTime[T](logger: Logger, msg: String)(f: => T): T = { 9 | val (duration, result) = timed(f) 10 | logger.info(s"$msg in ${prettyPrint(duration)}") 11 | result 12 | } 13 | 14 | def timed[T](f: => T): (Duration, T) = { 15 | val start = System.currentTimeMillis 16 | val result = f 17 | val duration = 18 | Duration(System.currentTimeMillis - start, TimeUnit.MILLISECONDS) 19 | (duration, result) 20 | } 21 | 22 | def prettyPrint(duration: Duration): String = { 23 | def plural(n: Long, word: String): String = 24 | if (n == 1) s"1 $word" else s"$n ${word}s" 25 | 26 | duration match { 27 | case duration if duration.toSeconds == 0 => 28 | plural(duration.toMillis, "millisecond") 29 | case duration if duration.toMinutes == 0 => 30 | plural(duration.toSeconds, "second") 31 | case duration if duration.toHours == 0 => 32 | plural(duration.toMinutes, "minute") 33 | case duration if duration.toDays == 0 => plural(duration.toHours, "hour") 34 | case duration if duration.toDays < 30 => plural(duration.toDays, "day") 35 | case duration if duration.toDays >= 30 && duration.toDays <= 365 => 36 | plural(duration.toDays / 30, "month") 37 | case duration if duration.toDays > 365 => 38 | plural(duration.toDays / 365, "year") 39 | case _ => duration.toString() 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/VirtualMachineManagerProvider.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import com.microsoft.java.debug.core.adapter.IVirtualMachineManagerProvider 4 | import com.sun.jdi.Bootstrap 5 | import com.sun.jdi.VirtualMachineManager 6 | 7 | object VirtualMachineManagerProvider extends IVirtualMachineManagerProvider { 8 | def getVirtualMachineManager: VirtualMachineManager = 9 | Bootstrap.virtualMachineManager 10 | } 11 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/DebuggeeInvocationException.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.evaluator 2 | 3 | private[internal] case class DebuggeeInvocationException( 4 | message: String, 5 | remoteException: Option[JdiObject] 6 | ) extends Exception(message) 7 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/ExpressionCompiler.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.evaluator 2 | 3 | import ch.epfl.scala.debugadapter.ScalaVersion 4 | import ch.epfl.scala.debugadapter.internal.Errors 5 | 6 | import java.lang.reflect.InvocationTargetException 7 | import java.lang.reflect.Method 8 | import java.nio.file.Path 9 | import java.util.function.Consumer 10 | import scala.collection.mutable.Buffer 11 | import scala.jdk.CollectionConverters.* 12 | import scala.util.Failure 13 | import scala.util.Success 14 | import scala.util.Try 15 | 16 | private[debugadapter] class ExpressionCompiler( 17 | instance: Any, 18 | compileMethod: Method, 19 | val scalaVersion: ScalaVersion, 20 | scalacOptions: Seq[String], 21 | classPath: String 22 | ) { 23 | def compile( 24 | outDir: Path, 25 | expressionClassName: String, 26 | sourceFile: Path, 27 | line: Int, 28 | expression: String, 29 | localNames: Set[String], 30 | pckg: String, 31 | testMode: Boolean 32 | ): Try[Unit] = { 33 | try { 34 | val errors = Buffer.empty[String] 35 | val res = compileMethod 36 | .invoke( 37 | instance, 38 | outDir, 39 | expressionClassName, 40 | classPath, 41 | scalacOptions.toArray, 42 | sourceFile, 43 | line: java.lang.Integer, 44 | expression, 45 | localNames.asJava, 46 | pckg, 47 | { error => errors += error }: Consumer[String], 48 | testMode: java.lang.Boolean 49 | ) 50 | .asInstanceOf[Boolean] 51 | if (res) Success(()) else Failure(Errors.compilationFailure(errors.toSeq)) 52 | } catch { 53 | case cause: InvocationTargetException => Failure(cause.getCause()) 54 | } 55 | } 56 | } 57 | 58 | private[debugadapter] object ExpressionCompiler { 59 | def apply( 60 | scalaVersion: ScalaVersion, 61 | scalacOptions: Seq[String], 62 | classPath: String, 63 | classLoader: ClassLoader 64 | ): Try[ExpressionCompiler] = { 65 | val className = 66 | if (scalaVersion.isScala2) "scala.tools.nsc.ExpressionCompilerBridge" 67 | else "dotty.tools.dotc.ExpressionCompilerBridge" 68 | 69 | try { 70 | val clazz = Class.forName(className, true, classLoader) 71 | val instance = clazz.getDeclaredConstructor().newInstance() 72 | val method = clazz.getMethods.find(_.getName == "run").get 73 | Success(new ExpressionCompiler(instance, method, scalaVersion, scalacOptions, classPath)) 74 | } catch { 75 | case cause: Throwable => Failure(cause) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiArray.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.evaluator 2 | 3 | import com.sun.jdi.ArrayReference 4 | import com.sun.jdi.ThreadReference 5 | import com.sun.jdi.Value 6 | 7 | import scala.jdk.CollectionConverters.* 8 | 9 | class JdiArray(arrayRef: ArrayReference, thread: ThreadReference) extends JdiObject(arrayRef, thread) { 10 | def setValue(index: Int, value: Value): Unit = 11 | arrayRef.setValue(index, value) 12 | 13 | def setValues(values: Seq[JdiValue]): Unit = arrayRef.setValues(values.map(_.value).asJava) 14 | 15 | def getValues: Seq[JdiValue] = arrayRef.getValues.asScala.toSeq.map(JdiValue(_, thread)) 16 | 17 | def getValue(i: Int): Safe[JdiValue] = 18 | Safe(JdiValue(arrayRef.getValue(i), thread)).recoverWith { case e: IndexOutOfBoundsException => 19 | Safe.failed(DebuggeeInvocationException(e.getMessage, None)) 20 | } 21 | } 22 | 23 | object JdiArray { 24 | def apply(arrayValue: Value, thread: ThreadReference): JdiArray = 25 | new JdiArray(arrayValue.asInstanceOf[ArrayReference], thread) 26 | } 27 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiClass.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.evaluator 2 | 3 | import com.sun.jdi.* 4 | 5 | import scala.jdk.CollectionConverters.* 6 | import scala.util.control.NonFatal 7 | 8 | private[internal] class JdiClass( 9 | val cls: ClassType, 10 | thread: ThreadReference 11 | ) extends JdiObject(cls.classObject, thread) { 12 | 13 | def initialized: Boolean = cls.isInitialized 14 | def className: String = cls.name 15 | override def classLoader: JdiClassLoader = JdiClassLoader(cls.classLoader, thread) 16 | 17 | def newInstance(args: Seq[JdiValue]): Safe[JdiObject] = { 18 | val ctr = cls.methodsByName("").get(0) 19 | newInstance(ctr, args) 20 | } 21 | 22 | def newInstance(signature: String, args: Seq[JdiValue]): Safe[JdiObject] = { 23 | val ctr = cls.methodsByName("", signature).get(0) 24 | newInstance(ctr, args) 25 | } 26 | 27 | def newInstance(ctr: Method, args: Seq[JdiValue]): Safe[JdiObject] = 28 | for { 29 | _ <- prepareMethod(ctr) 30 | instance <- Safe(cls.newInstance(thread, ctr, args.map(_.value).asJava, ObjectReference.INVOKE_SINGLE_THREADED)) 31 | .recoverWith(extractMessage) 32 | } yield JdiObject(instance, thread) 33 | 34 | // Load the argument types of the method to avoid ClassNotLoadedException 35 | // TODO Should we use this method before all invocations: methods, ctrs, fields 36 | private def prepareMethod(method: Method): Safe[Unit] = { 37 | def loadArgumentsRecursively(): Safe[Unit] = { 38 | try { 39 | method.argumentTypes() 40 | Safe(()) 41 | } catch { 42 | case exception: ClassNotLoadedException => 43 | val className = exception.className.stripSuffix("[]").replace('/', '.') 44 | classLoader.loadClass(className).flatMap(_ => loadArgumentsRecursively()) 45 | case NonFatal(cause) => Safe(throw cause) 46 | } 47 | } 48 | loadArgumentsRecursively() 49 | } 50 | 51 | def getStaticField(fieldName: String): Safe[JdiValue] = 52 | Safe(cls.getValue(cls.fieldByName(fieldName))).map(JdiValue(_, thread)) 53 | 54 | def invokeStatic(methodName: String, args: Seq[JdiValue]): Safe[JdiValue] = { 55 | val method = cls.methodsByName(methodName).get(0) 56 | invokeStatic(method, args) 57 | } 58 | 59 | def invokeStatic(methodName: String, signature: String, args: Seq[JdiValue]): Safe[JdiValue] = { 60 | val method = cls.methodsByName(methodName, signature).get(0) 61 | invokeStatic(method, args) 62 | } 63 | 64 | def invokeStatic(method: Method, args: Seq[JdiValue]): Safe[JdiValue] = 65 | Safe(cls.invokeMethod(thread, method, args.map(_.value).asJava, ObjectReference.INVOKE_SINGLE_THREADED)) 66 | .map(JdiValue(_, thread)) 67 | .recoverWith(extractMessage) 68 | } 69 | 70 | object JdiClass { 71 | def apply(classType: Type, thread: ThreadReference): JdiClass = 72 | new JdiClass(classType.asInstanceOf[ClassType], thread) 73 | 74 | def apply(classObject: Value, thread: ThreadReference): JdiClass = 75 | JdiClass(classObject.asInstanceOf[ClassObjectReference].reflectedType, thread) 76 | } 77 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiFrame.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.evaluator 2 | 3 | import com.microsoft.java.debug.core.DebugException 4 | import com.sun.jdi.* 5 | 6 | import scala.jdk.CollectionConverters.* 7 | 8 | final case class JdiFrame(thread: ThreadReference, depth: Int) { 9 | def current(): StackFrame = thread.frame(depth) 10 | 11 | // it's a Safe because it can fail, but it could also be a Try 12 | def classLoader(): Safe[JdiClassLoader] = { 13 | def getClassLoaderRecursively(depth: Int): Option[ClassLoaderReference] = 14 | if (depth == thread.frameCount) None 15 | else { 16 | Option(thread.frame(depth).location.method.declaringType.classLoader) 17 | .orElse(getClassLoaderRecursively(depth + 1)) 18 | } 19 | 20 | Safe { 21 | val classLoader = getClassLoaderRecursively(depth) 22 | .getOrElse(throw new DebugException("Cannot find any class loader in the stack trace")) 23 | JdiClassLoader(classLoader, thread) 24 | } 25 | } 26 | 27 | // this object can be null 28 | lazy val thisObject: Option[JdiObject] = 29 | Option(current().thisObject).map(JdiObject(_, thread)) 30 | 31 | def variables(): Seq[LocalVariable] = 32 | try current().visibleVariables.asScala.toSeq 33 | catch { 34 | case _: AbsentInformationException => Seq.empty 35 | } 36 | 37 | def variablesAndValues(): Seq[(LocalVariable, JdiValue)] = 38 | variables().map(v => v -> JdiValue(current().getValue(v), thread)) 39 | 40 | def variableByName(name: String): Option[LocalVariable] = 41 | variables().find(_.name == name) 42 | 43 | def variableValue(variable: LocalVariable): JdiValue = 44 | JdiValue(current().getValue(variable), thread) 45 | 46 | def setVariable(variable: LocalVariable, value: JdiValue): Unit = 47 | current().setValue(variable, value.value) 48 | 49 | def getPrimitiveBoxedClass(pt: PrimitiveType): ReferenceType = { 50 | val vm = current().virtualMachine() 51 | def cls(name: String) = vm.classesByName(name).get(0) 52 | pt match { 53 | case _: BooleanType => cls("java.lang.Boolean") 54 | case _: ByteType => cls("java.lang.Byte") 55 | case _: CharType => cls("java.lang.Character") 56 | case _: DoubleType => cls("java.lang.Double") 57 | case _: FloatType => cls("java.lang.Float") 58 | case _: IntegerType => cls("java.lang.Integer") 59 | case _: LongType => cls("java.lang.Long") 60 | case _: ShortType => cls("java.lang.Short") 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiNull.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.evaluator 2 | 3 | object JdiNull extends JdiObject(null, null) 4 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiObject.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.evaluator 2 | 3 | import com.sun.jdi.* 4 | 5 | import scala.jdk.CollectionConverters.* 6 | 7 | private[evaluator] class JdiObject( 8 | val reference: ObjectReference, 9 | thread: ThreadReference 10 | ) extends JdiValue(reference, thread) { 11 | def getField(field: Field): JdiValue = JdiValue(reference.getValue(field), thread) 12 | 13 | def getField(name: String): JdiValue = getField(reference.referenceType.fieldByName(name)) 14 | 15 | def invoke(methodName: String, args: Seq[JdiValue]): Safe[JdiValue] = { 16 | val m = reference.referenceType.methodsByName(methodName).get(0) 17 | invoke(m, args) 18 | } 19 | 20 | def invoke(methodName: String, signature: String, args: Seq[JdiValue]): Safe[JdiValue] = { 21 | val m = reference.referenceType.methodsByName(methodName, signature).get(0) 22 | invoke(m, args) 23 | } 24 | 25 | def classObject: JdiClass = JdiClass(reference.referenceType, thread) 26 | def classLoader: JdiClassLoader = JdiClassLoader(reference.referenceType.classLoader, thread) 27 | 28 | def invoke(method: Method, args: Seq[JdiValue]): Safe[JdiValue] = 29 | Safe(reference.invokeMethod(thread, method, args.map(_.value).asJava, ObjectReference.INVOKE_SINGLE_THREADED)) 30 | .map(JdiValue(_, thread)) 31 | .recoverWith(extractMessage) 32 | 33 | protected val extractMessage: PartialFunction[Throwable, Safe[Nothing]] = { 34 | case invocationException: InvocationException => 35 | for { 36 | exception <- Safe(invocationException.exception).map(JdiObject(_, thread)) 37 | message <- exception.invoke("toString", List()).map(_.asString.stringValue).recover { case _ => "" } 38 | } yield throw new DebuggeeInvocationException(message, Some(exception)) 39 | } 40 | 41 | // we use a Seq instead of a Map because the ScalaEvaluator rely on the order of the fields 42 | def fields: Seq[(String, JdiValue)] = 43 | reference.referenceType.fields.asScala.toSeq.map(f => (f.name, getField(f))) 44 | } 45 | 46 | private[internal] object JdiObject { 47 | def apply(value: Value, thread: ThreadReference): JdiObject = 48 | new JdiObject(value.asInstanceOf[ObjectReference], thread) 49 | } 50 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiString.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.evaluator 2 | 3 | import com.sun.jdi._ 4 | 5 | private[internal] class JdiString(ref: StringReference, thread: ThreadReference) extends JdiValue(ref, thread) { 6 | def stringValue: String = ref.value 7 | } 8 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/MessageLogger.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.evaluator 2 | 3 | import com.sun.jdi.* 4 | 5 | import scala.util.Try 6 | 7 | private[internal] object MessageLogger { 8 | def log(logMessage: PlainLogMessage, frame: JdiFrame): Try[Value] = { 9 | val result = for { 10 | classLoader <- frame.classLoader() 11 | arg <- classLoader.mirrorOf(logMessage.message) 12 | predefClass <- classLoader.loadClass("scala.Predef$") 13 | predef <- predefClass.getStaticField("MODULE$").map(_.asObject) 14 | res <- predef.invoke("println", "(Ljava/lang/Object;)V", List(arg)) 15 | } yield res.value 16 | result.getResult 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/PreparedExpression.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.evaluator 2 | 3 | import java.nio.file.Path 4 | 5 | sealed trait PreparedExpression 6 | final case class CompiledExpression(classDir: Path, className: String) extends PreparedExpression 7 | final case class RuntimeExpression(tree: RuntimeEvaluationTree) extends PreparedExpression 8 | final case class PlainLogMessage(message: String) extends PreparedExpression 9 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.evaluator 2 | 3 | import ch.epfl.scala.debugadapter.Logger 4 | import ch.epfl.scala.debugadapter.internal.SourceLookUpProvider 5 | import com.sun.jdi.Value 6 | 7 | import scala.util.Failure 8 | import scala.util.Success 9 | import scala.util.Try 10 | 11 | class RuntimeEvaluator(sourceLookUp: SourceLookUpProvider, logger: Logger) { 12 | def validate(expression: String, frame: JdiFrame, preEvaluation: Boolean): Try[RuntimeExpression] = { 13 | val validation = new RuntimeValidation(frame, sourceLookUp, preEvaluation)(logger) 14 | validation.validate(expression) match { 15 | case invalid: Invalid => Failure(invalid.exception) 16 | case Valid(expr) => Success(RuntimeExpression(expr)) 17 | } 18 | } 19 | 20 | def evaluate(expression: RuntimeExpression, frame: JdiFrame): Try[Value] = { 21 | val evaluation = new RuntimeEvaluation(frame, logger) 22 | evaluation.evaluate(expression.tree).getResult.map(_.value) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/Safe.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.evaluator 2 | 3 | import com.sun.jdi._ 4 | 5 | import scala.util.{Failure, Success, Try} 6 | 7 | /** 8 | * Objects created on the remote JVM can be garbage-collected at any time. 9 | * https://stackoverflow.com/questions/25793688/life-span-of-jdi-mirrors-of-objects-living-in-a-remote-jvm 10 | * 11 | * This can be prevented by wrapping every object reference into a [[Safe]] 12 | * instance. It calls `disableCollection` at construction and `enableCollection` 13 | * when the final result is retrieved. 14 | * 15 | * You can get the result out of a [[Safe]] instance by calling `getResult`. 16 | * Then the object references are not protected anymore and can be 17 | * normally garbage collected. 18 | */ 19 | class Safe[+A] private ( 20 | private val result: Try[A], 21 | private val dispose: () => Unit 22 | ) { 23 | def extract[B](f: A => B): Try[B] = result.map(f) 24 | def extract: Try[A] = result 25 | 26 | def map[B](f: A => B): Safe[B] = new Safe(result.map(f), dispose) 27 | 28 | def flatMap[B](f: A => Safe[B]): Safe[B] = { 29 | result match { 30 | case Failure(exception) => new Safe(Failure(exception), dispose) 31 | case Success(a) => 32 | val b = f(a) 33 | new Safe(b.result, () => { dispose(); b.dispose() }) 34 | } 35 | } 36 | 37 | def orElse[B >: A](alternative: => Safe[B]): Safe[B] = 38 | result match { 39 | case Failure(_) => alternative 40 | case _ => this 41 | } 42 | 43 | def getResult: Try[A] = { 44 | dispose() 45 | result 46 | } 47 | 48 | def withFilter(p: A => Boolean): Safe[A] = { 49 | new Safe(result.withFilter(p).map(identity), dispose) 50 | } 51 | 52 | def withFilterNot(p: A => Boolean): Safe[A] = withFilter(!p(_)) 53 | 54 | def recover[B >: A](f: PartialFunction[Throwable, B]): Safe[B] = { 55 | new Safe(result.recover(f), dispose) 56 | } 57 | 58 | def recoverWith[B >: A](f: PartialFunction[Throwable, Safe[B]]): Safe[B] = { 59 | result match { 60 | case Failure(exception) if f.isDefinedAt(exception) => 61 | val b = f(exception) 62 | new Safe( 63 | b.result, 64 | () => { 65 | dispose(); b.dispose() 66 | } 67 | ) 68 | case _ => new Safe(result, dispose) 69 | } 70 | } 71 | } 72 | 73 | object Safe { 74 | def apply[A](f: => A): Safe[A] = 75 | apply(Try(f)) 76 | 77 | def apply[A](a: Try[A]): Safe[A] = { 78 | a match { 79 | case null => new Safe(Success(null).asInstanceOf, () => ()) 80 | case Success(value) => 81 | value match { 82 | case obj: ObjectReference => 83 | new Safe( 84 | Try(obj.disableCollection()).map(_ => value), 85 | () => Try(obj.enableCollection()) 86 | ) 87 | case _ => new Safe(Success(value), () => ()) 88 | } 89 | case Failure(exception) => 90 | new Safe(Failure(exception), () => ()) 91 | } 92 | } 93 | 94 | def unapply[A](safe: Safe[A]): Option[Try[A]] = Some(safe.result) 95 | 96 | def join[A, B](safeA: Safe[A], safeB: Safe[B]): Safe[(A, B)] = { 97 | safeA.flatMap(a => safeB.map(b => (a, b))) 98 | } 99 | 100 | def failed(exception: Throwable): Safe[Nothing] = { 101 | new Safe(Failure(exception), () => ()) 102 | } 103 | 104 | def successful[A](a: A): Safe[A] = { 105 | new Safe(Success(a), () => ()) 106 | } 107 | 108 | def failed(message: String): Safe[Nothing] = { 109 | failed(new Exception(message)) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/package.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import scala.util.Try 4 | import scala.util.Failure 5 | import scala.util.Success 6 | import ch.epfl.scala.debugadapter.Logger 7 | 8 | package object evaluator { 9 | implicit class SafeSeq[A](seq: Seq[Safe[A]]) { 10 | def traverse: Safe[Seq[A]] = { 11 | seq.foldRight(Safe(Seq.empty[A])) { (safeHead, safeTail) => 12 | safeTail.flatMap(tail => safeHead.map(head => head +: tail)) 13 | } 14 | } 15 | } 16 | 17 | implicit class SafeOption[A](opt: Option[Safe[A]]) { 18 | def traverse: Safe[Option[A]] = 19 | opt.map(s => s.map(Option.apply)).getOrElse(Safe(None)) 20 | } 21 | 22 | implicit class TryToSafe[A](t: Try[A]) { 23 | def toSafe: Safe[A] = Safe(t) 24 | def toSeq: Seq[A] = t match { 25 | case Failure(exception) => Seq() 26 | case Success(value) => Seq(value) 27 | } 28 | } 29 | 30 | implicit class ValidationSeq[A](seq: Seq[Validation[A]])(implicit logger: Logger) { 31 | def traverse: Validation[Seq[A]] = { 32 | seq.foldRight(Validation(Seq.empty[A])) { (safeHead, safeTail) => 33 | safeTail.flatMap(tail => safeHead.map(head => head +: tail)) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/scalasig/Flags.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.scalasig 2 | 3 | /** 4 | * Originally copied from https://github.com/JetBrains/intellij-scala 5 | * https://github.com/JetBrains/intellij-scala/blob/074e8f98d9789b3e7def3ade8d39e7ae770beccf/scala/decompiler/src/org/jetbrains/plugins/scala/decompiler/scalasig/Flags.scala 6 | */ 7 | trait Flags { 8 | def hasFlag(flag: Long): Boolean 9 | 10 | def isImplicit: Boolean = hasFlag(0x00000001) 11 | def isFinal: Boolean = hasFlag(0x00000002) 12 | def isPrivate: Boolean = hasFlag(0x00000004) 13 | def isProtected: Boolean = hasFlag(0x00000008) 14 | 15 | def isSealed: Boolean = hasFlag(0x00000010) 16 | def isOverride: Boolean = hasFlag(0x00000020) 17 | def isCase: Boolean = hasFlag(0x00000040) 18 | def isAbstract: Boolean = hasFlag(0x00000080) 19 | 20 | def isDeferred: Boolean = hasFlag(0x00000100) 21 | def isMethod: Boolean = hasFlag(0x00000200) 22 | def isModule: Boolean = hasFlag(0x00000400) 23 | def isInterface: Boolean = hasFlag(0x00000800) 24 | 25 | def isMutable: Boolean = hasFlag(0x00001000) 26 | def isParam: Boolean = hasFlag(0x00002000) 27 | def isPackage: Boolean = hasFlag(0x00004000) 28 | def isDeprecated: Boolean = hasFlag(0x00008000) 29 | 30 | def isCovariant: Boolean = hasFlag(0x00010000) 31 | def isCaptured: Boolean = hasFlag(0x00010000) 32 | 33 | def isByNameParam: Boolean = hasFlag(0x00010000) 34 | def isContravariant: Boolean = hasFlag(0x00020000) 35 | def isLabel: Boolean = hasFlag( 36 | 0x00020000 37 | ) // method symbol is a label. Set by TailCall 38 | 39 | // Solution for constructors 40 | def isInConstructor: Boolean = hasFlag( 41 | 0x00020000 42 | ) // class symbol is defined in this/superclass constructor 43 | 44 | def isAbstractOverride: Boolean = hasFlag(0x00040000) 45 | def isLocal: Boolean = hasFlag(0x00080000) 46 | 47 | def isJava: Boolean = hasFlag(0x00100000) 48 | // Same as in the java debug => doesn't work as intended 49 | def isSynthetic: Boolean = hasFlag(0x00200000) 50 | 51 | def isStable: Boolean = hasFlag(0x00400000) 52 | def isStatic: Boolean = hasFlag(0x00800000) 53 | 54 | def isCaseAccessor: Boolean = hasFlag(0x01000000) 55 | // Should work for traits, not sure 56 | def isTrait: Boolean = hasFlag(0x02000000) 57 | def hasDefault: Boolean = hasFlag(0x02000000) 58 | 59 | // Solution for bridges 60 | def isBridge: Boolean = hasFlag(0x04000000) 61 | def isAccessor: Boolean = hasFlag(0x08000000) 62 | 63 | def isSuperAccessor: Boolean = hasFlag(0x10000000) 64 | def isParamAccessor: Boolean = hasFlag(0x20000000) 65 | 66 | def isModuleVar: Boolean = hasFlag( 67 | 0x40000000 68 | ) // for variables: is the variable caching a module value 69 | // Probably a solution for synthetic 70 | def isSyntheticMethod: Boolean = hasFlag( 71 | 0x40000000 72 | ) // for methods: synthetic method, but without SYNTHETIC flag 73 | def isMonomorphic: Boolean = hasFlag( 74 | 0x40000000 75 | ) // for type symbols: does not have type parameters 76 | def isLazy: Boolean = hasFlag( 77 | 0x80000000L 78 | ) // symbol is a lazy val. can't have MUTABLE unless transformed by typer 79 | 80 | def isError: Boolean = hasFlag(0x100000000L) 81 | def isOverloaded: Boolean = hasFlag(0x200000000L) 82 | def isLifted: Boolean = hasFlag(0x400000000L) 83 | 84 | // TODO: Test for mixin 85 | def isMixedIn: Boolean = hasFlag(0x800000000L) 86 | def isExistential: Boolean = hasFlag(0x800000000L) 87 | 88 | def isExpandedName: Boolean = hasFlag(0x1000000000L) 89 | def isImplementationClass: Boolean = hasFlag(0x2000000000L) 90 | def isPreSuper: Boolean = hasFlag(0x2000000000L) 91 | 92 | } 93 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/scalasig/Ref.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.scalasig 2 | 3 | import ch.epfl.scala.debugadapter.internal.Errors 4 | 5 | import scala.language.implicitConversions 6 | import scala.reflect.ClassTag 7 | 8 | /** 9 | * Originally copied from https://github.com/JetBrains/intellij-scala 10 | * https://github.com/JetBrains/intellij-scala/blob/074e8f98d9789b3e7def3ade8d39e7ae770beccf/scala/decompiler/src/org/jetbrains/plugins/scala/decompiler/scalasig/Ref.scala 11 | * 12 | * Nikolay.Tropin 13 | * 19-Jul-17 14 | */ 15 | class Ref[T <: Entry: ClassTag](val index: Int)(implicit 16 | val scalaSig: ScalaSig 17 | ) { 18 | 19 | def get: T = { 20 | val entry = scalaSig.get(index) 21 | 22 | if (!scalaSig.isInitialized) 23 | throw Errors.frameDecodingFailure("usage of scalaSig entry before initialization") 24 | 25 | val expectedClass = implicitly[ClassTag[T]].runtimeClass 26 | if (!expectedClass.isInstance(entry)) { 27 | val expName = expectedClass.getCanonicalName 28 | val actName = entry.getClass.getCanonicalName 29 | val message = s"wrong type of reference at index $index, expected: $expName, actual: $actName" 30 | throw Errors.frameDecodingFailure(message) 31 | } 32 | 33 | entry.asInstanceOf[T] 34 | } 35 | 36 | override def toString: String = 37 | if (!scalaSig.isInitialized) "not initialized" 38 | else get.toString 39 | 40 | override def equals(obj: scala.Any): Boolean = obj match { 41 | case r: Ref[_] => r.index == index 42 | case _ => false 43 | } 44 | 45 | override def hashCode(): Int = index 46 | } 47 | 48 | class MappedRef[T <: Entry: ClassTag, S <: Entry: ClassTag]( 49 | val ref: Ref[T], 50 | val fun: T => S 51 | )(implicit override val scalaSig: ScalaSig) 52 | extends Ref[S](ref.index) { 53 | 54 | override def get: S = fun(ref.get) 55 | } 56 | 57 | object Ref { 58 | def to[T <: Entry: ClassTag](index: Int)(implicit scalaSig: ScalaSig) = 59 | new Ref[T](index) 60 | 61 | def unapply[T <: Entry](ref: Ref[T]): Option[T] = Some(ref.get) 62 | 63 | implicit def unwrap[T <: Entry](ref: Ref[T]): T = ref.get 64 | 65 | implicit def unwrapSeq[T <: Entry](refs: Seq[Ref[T]]): Seq[T] = 66 | refs.map(_.get) 67 | 68 | implicit def unwrapOption[T <: Entry](ref: Option[Ref[T]]): Option[T] = 69 | ref.map(_.get) 70 | 71 | implicit class RefOps[T <: Entry: ClassTag](ref: Ref[T]) { 72 | import ref.scalaSig 73 | 74 | def map[S <: Entry: ClassTag](fun: T => S): Ref[S] = 75 | new MappedRef[T, S](ref, fun) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/scalasig/ScalaSig.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.scalasig 2 | 3 | import scala.collection.mutable 4 | import scala.collection.mutable.ArrayBuffer 5 | 6 | /** 7 | * Originally copied from https://github.com/JetBrains/intellij-scala 8 | * https://github.com/JetBrains/intellij-scala/blob/074e8f98d9789b3e7def3ade8d39e7ae770beccf/scala/decompiler/src/org/jetbrains/plugins/scala/decompiler/scalasig/ScalaSig.scala 9 | * 10 | * Nikolay.Tropin 11 | * 19-Jul-17 12 | */ 13 | class ScalaSig(val entries: Array[Entry]) { 14 | private var initialized: Boolean = false 15 | 16 | def get(idx: Int): Entry = entries(idx) 17 | 18 | def isInitialized: Boolean = initialized 19 | def finished(): Unit = initialized = true 20 | 21 | private val classes = ArrayBuffer.empty[ClassSymbol] 22 | private val objects = ArrayBuffer.empty[ObjectSymbol] 23 | private val symAnnots = ArrayBuffer.empty[SymAnnot] 24 | private val parentToChildren = mutable.HashMap.empty[Int, ArrayBuffer[Symbol]] 25 | 26 | def topLevelClasses: Iterable[ClassSymbol] = classes.filter(isTopLevelClass) 27 | def topLevelObjects: Iterable[ObjectSymbol] = objects.filter(isTopLevel) 28 | 29 | def findCompanionClass(objectSymbol: ObjectSymbol): Option[ClassSymbol] = { 30 | val owner: Symbol = objectSymbol.symbolInfo.owner.get 31 | val name = objectSymbol.name 32 | classes.find(c => c.info.owner.get.eq(owner) && c.name == name) 33 | } 34 | 35 | def children(symbol: ScalaSigSymbol): Iterable[Symbol] = { 36 | parentToChildren.keysIterator.find(get(_) eq symbol) match { 37 | case None => Iterable.empty 38 | case Some(i) => parentToChildren(i) 39 | } 40 | } 41 | 42 | def attributes(symbol: ScalaSigSymbol): Iterable[SymAnnot] = { 43 | def sameSymbol(ann: SymAnnot) = (ann.symbol.get, symbol) match { 44 | case (s, t) if s == t => true 45 | case (m1: MethodSymbol, m2: MethodSymbol) if equiv(m1, m2) => true 46 | case _ => false 47 | } 48 | val forSameSymbol = symAnnots.filter(sameSymbol) 49 | forSameSymbol.groupBy(_.typeRef).values.map(_.head) 50 | } 51 | 52 | def addClass(c: ClassSymbol): Unit = classes += c 53 | def addObject(o: ObjectSymbol): Unit = objects += o 54 | def addAttribute(a: SymAnnot): Unit = symAnnots += a 55 | 56 | def addChild(parent: Option[Ref[Symbol]], child: Symbol): Unit = { 57 | parent.foreach { ref => 58 | val children = 59 | parentToChildren.getOrElseUpdate(ref.index, ArrayBuffer.empty) 60 | children += child 61 | } 62 | } 63 | 64 | private def isTopLevel(symbol: Symbol): Boolean = symbol.parent match { 65 | case Some(_: ExternalSymbol) => true 66 | case _ => false 67 | } 68 | private def isTopLevelClass(symbol: Symbol): Boolean = 69 | !symbol.isModule && isTopLevel(symbol) 70 | 71 | private def equiv(m1: MethodSymbol, m2: MethodSymbol) = { 72 | def unwrapType(t: Type) = t match { 73 | case NullaryMethodType(Ref(tp)) => tp 74 | case _ => t 75 | } 76 | 77 | m1.name == m2.name && m1.parent == m2.parent && 78 | unwrapType(m1.infoType) == unwrapType(m2.infoType) 79 | } 80 | 81 | def syntheticSymbols(): Seq[Symbol] = 82 | parentToChildren.valuesIterator.flatten.filter(_.isSynthetic).toList 83 | } 84 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/scalasig/ScalaSigReader.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.scalasig 2 | 3 | import java.nio.charset.StandardCharsets 4 | 5 | /** 6 | * Originally copied from https://github.com/JetBrains/intellij-scala 7 | * https://github.com/JetBrains/intellij-scala/blob/074e8f98d9789b3e7def3ade8d39e7ae770beccf/scala/decompiler/src/org/jetbrains/plugins/scala/decompiler/scalasig/ScalaSigReader.scala 8 | * 9 | * Nikolay.Tropin 10 | * 19-Jul-17 11 | */ 12 | 13 | //Based on scala.reflect.internal.pickling.PickleBuffer 14 | class ScalaSigReader(bytes: Array[Byte]) { 15 | var readIndex = 0 16 | 17 | skipVersion() 18 | 19 | /** Read a byte */ 20 | def readByte(): Int = { 21 | val x = bytes(readIndex).toInt; readIndex += 1; x 22 | } 23 | 24 | /** 25 | * Read a natural number in big endian format, base 128. 26 | * All but the last digits have bit 0x80 set. 27 | */ 28 | def readNat(): Int = readLongNat().toInt 29 | 30 | def readLongNat(): Long = { 31 | var b = 0L 32 | var x = 0L 33 | def next(): Unit = { 34 | b = readByte().toLong 35 | x = (x << 7) + (b & 0x7f) 36 | } 37 | 38 | next() 39 | while ((b & 0x80) != 0L) next() 40 | x 41 | } 42 | 43 | /** Read a long number in signed big endian format, base 256. */ 44 | def readLong(len: Int): Long = { 45 | var x = 0L 46 | var i = 0 47 | while (i < len) { 48 | x = (x << 8) + (readByte() & 0xff) 49 | i += 1 50 | } 51 | val leading = 64 - (len << 3) 52 | x << leading >> leading 53 | } 54 | 55 | def readUtf8(length: Int): String = { 56 | val savedIndex = readIndex 57 | readIndex += length 58 | new String(bytes, savedIndex, length, StandardCharsets.UTF_8) 59 | } 60 | 61 | def until[T](end: Int, op: () => T): List[T] = { 62 | if (readIndex >= end) List() 63 | else { 64 | val startIdx = readIndex 65 | val result = op() 66 | 67 | if (startIdx == readIndex) List() 68 | else result :: until(end, op) 69 | } 70 | } 71 | 72 | /** 73 | * Pickle = majorVersion_Nat minorVersion_Nat nbEntries_Nat {Entry} 74 | * Entry = type_Nat length_Nat [actual entries] 75 | * 76 | * Assumes that the ..Version_Nat are already consumed. 77 | * 78 | * @return an array mapping entry numbers to locations in 79 | * the byte array where the entries start. 80 | */ 81 | def createIndex(): Array[Int] = { 82 | val size = readNat() 83 | val index = new Array[Int](size) // nbEntries_Nat 84 | var i = 0 85 | while (i < size) { 86 | index(i) = readIndex 87 | readByte() // skip type_Nat 88 | readIndex = readNat() + readIndex // read length_Nat, jump to next entry 89 | i += 1 90 | } 91 | index 92 | } 93 | 94 | private def skipVersion(): Unit = { 95 | val major = readNat() 96 | val minor = readNat() 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/scalasig/TagGroups.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.scalasig 2 | 3 | import scala.reflect.internal.pickling.PickleFormat._ 4 | 5 | /** 6 | * Originally copied from https://github.com/JetBrains/intellij-scala 7 | * https://github.com/JetBrains/intellij-scala/blob/074e8f98d9789b3e7def3ade8d39e7ae770beccf/scala/decompiler/src/org/jetbrains/plugins/scala/decompiler/scalasig/TagGroups.scala 8 | * 9 | * Nikolay.Tropin 10 | * 19-Jul-17 11 | */ 12 | object TagGroups { 13 | def isSymbolTag(tag: Int): Boolean = 14 | firstSymTag <= tag && tag <= lastExtSymTag 15 | 16 | def isConstantTag(tag: Int): Boolean = 17 | tag >= LITERALunit && tag <= LITERALenum 18 | 19 | def isAnnotArgTag(tag: Int): Boolean = tag == TREE || isConstantTag(tag) 20 | 21 | def isConstAnnotArgTag(tag: Int): Boolean = isConstantTag( 22 | tag 23 | ) || tag == TREE || tag == ANNOTINFO || tag == ANNOTATEDtree 24 | 25 | def isTypeTag(tag: Int): Boolean = 26 | tag >= NOtpe && tag <= IMPLICITMETHODtpe || 27 | tag == ANNOTATEDtpe || tag == DEBRUIJNINDEXtpe || 28 | tag == EXISTENTIALtpe || tag == SUPERtpe 29 | 30 | def isNameTag(tag: Int): Boolean = tag == TERMname || tag == TYPEname 31 | 32 | final val SUPERtpe2 = 52 // There is an inconsistency in PicklerFormat 33 | } 34 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/ClassLoadingFilter.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.stacktrace 2 | 3 | import com.sun.jdi.{Location, Method} 4 | import ch.epfl.scala.debugadapter.internal.ByteCode 5 | 6 | private[internal] object ClassLoadingFilter extends StepFilter { 7 | val classLoadingCodes = Set( 8 | ByteCode.NEW, 9 | ByteCode.ANEWARRAY, 10 | ByteCode.MULTIANEWARRAY, 11 | ByteCode.LDC, 12 | ByteCode.INSTANCEOF, 13 | ByteCode.CHECKCAST 14 | ) 15 | override def skipOut(upperLocation: Location, method: Method): Boolean = { 16 | val previousByteCode = upperLocation.method.bytecodes.apply(upperLocation.codeIndex.toInt) 17 | classLoadingCodes.contains(previousByteCode) && method.name != "" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/DecodedMethodBridge.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.stacktrace 2 | 3 | import ch.epfl.scala.debugadapter.internal.Errors 4 | import com.microsoft.java.debug.core.adapter.stacktrace.DecodedMethod 5 | 6 | import java.lang.reflect.InvocationTargetException 7 | 8 | class DecodedMethodBridge(instance: Any) extends DecodedMethod { 9 | 10 | override def format(): String = invoke[String]("format") 11 | 12 | override def isGenerated(): Boolean = invoke[Boolean]("isGenerated") 13 | 14 | private def invoke[T](methodName: String): T = 15 | try instance.getClass.getMethod(methodName).invoke(instance).asInstanceOf[T] 16 | catch { 17 | case e: InvocationTargetException => 18 | throw Errors.frameDecodingFailure(e.getCause) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/JavaMethod.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.stacktrace 2 | 3 | import com.sun.jdi 4 | import com.microsoft.java.debug.core.adapter.stacktrace.DecodedMethod 5 | import scala.jdk.CollectionConverters.* 6 | 7 | final case class JavaMethod(method: jdi.Method, isGenerated: Boolean) extends DecodedMethod { 8 | def format: String = { 9 | val declaringType = method.declaringType.name.split("\\.").last 10 | val argumentTypes = method.argumentTypeNames.asScala 11 | .map(arg => arg.split("\\.").last) 12 | .mkString(",") 13 | val returnType = method.returnTypeName.split("\\.").last 14 | s"$declaringType.${method.name}($argumentTypes): $returnType" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/JdiExtensions.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.stacktrace 2 | 3 | import com.sun.jdi 4 | import scala.jdk.CollectionConverters.* 5 | 6 | object JdiExtensions { 7 | private val lazyTypes: Set[String] = Set( 8 | "scala.runtime.LazyRef", 9 | "scala.runtime.LazyBoolean", 10 | "scala.runtime.LazyByte", 11 | "scala.runtime.LazyChar", 12 | "scala.runtime.LazyShort", 13 | "scala.runtime.LazyInt", 14 | "scala.runtime.LazyLong", 15 | "scala.runtime.LazyFloat", 16 | "scala.runtime.LazyDouble", 17 | "scala.runtime.LazyUnit" 18 | ) 19 | 20 | implicit class JdiMethodExtension(val method: jdi.Method) extends AnyVal { 21 | def isStaticMain: Boolean = 22 | method.isStatic && method.name == "main" 23 | 24 | def isJava: Boolean = 25 | method.declaringType.sourceName.endsWith(".java") 26 | 27 | def isConstructor: Boolean = 28 | method.name == "" 29 | 30 | def isStaticConstructor: Boolean = 31 | method.name == "" 32 | 33 | def isAnonFunction: Boolean = 34 | method.name.matches(".+\\$anonfun(\\$.+)?\\$\\d+") 35 | 36 | def isLiftedMethod: Boolean = 37 | method.name.matches(".+\\$\\d+") 38 | 39 | def isAdaptedMethod: Boolean = 40 | method.name.matches(".+\\$adapted(\\$\\d+)?") 41 | 42 | def isLazyInitializer: Boolean = 43 | method.name.contains("$lzyINIT") || method.name.contains("$lzycompute") 44 | 45 | def isLazyGetter: Boolean = 46 | method.argumentTypeNames.asScala.toSeq match { 47 | case Seq(argTypeName) => lazyTypes.contains(argTypeName) 48 | case _ => false 49 | } 50 | 51 | def isDefaultValue: Boolean = 52 | method.name.contains("$default$") 53 | 54 | def isTraitInitializer: Boolean = 55 | method.name == "$init$" 56 | 57 | def isPrivateAccessor: Boolean = 58 | method.name.matches(""".+\$access\$\d+""") 59 | } 60 | 61 | implicit class JdiReferenceTypeExtension(val tpe: jdi.ReferenceType) extends AnyVal { 62 | def isDynamicClass: Boolean = 63 | try 64 | // source of java.lang.invoke.LambdaForm$DMH.1175962212.invokeStatic_L_L(java.lang.Object, java.lang.Object) is LambdaForm$DMH 65 | !tpe.sourceName.contains('.') 66 | catch { 67 | case _: jdi.AbsentInformationException => 68 | // We assume that a ReferenceType with no source name is necessarily a dynamic class 69 | true 70 | } 71 | 72 | def isAnonClass: Boolean = 73 | tpe.name.contains("$anon$") 74 | 75 | /** is local class or local object */ 76 | def isLocalClass: Boolean = 77 | tpe.name.matches(".+\\$\\d+\\$?") 78 | 79 | def isNestedClass: Boolean = 80 | tpe.name.matches(".+\\$\\.+") 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/RuntimeStepFilter.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.stacktrace 2 | 3 | import com.sun.jdi.Method 4 | import com.sun.jdi.Location 5 | import ch.epfl.scala.debugadapter.ScalaVersion 6 | 7 | private[internal] class RuntimeStepFilter(classesToSkip: Set[String], methodsToSkip: Set[String]) extends StepFilter { 8 | override def skipOver(method: Method): Boolean = skip(method) 9 | override def skipOut(upperLocation: Location, method: Method): Boolean = skip(method) 10 | private def skip(method: Method): Boolean = 11 | classesToSkip.contains(method.declaringType.name) || methodsToSkip.contains(method.toString) 12 | } 13 | 14 | private[internal] object RuntimeStepFilter { 15 | private val javaClassesToSkip = Set("sun.misc.Unsafe") 16 | private val javaMethodsToSkip = Set( 17 | "java.lang.invoke.DirectMethodHandle.internalMemberName(java.lang.Object)", 18 | "java.lang.invoke.DirectMethodHandle.allocateInstance(java.lang.Object)", 19 | "java.lang.invoke.DirectMethodHandle.constructorMethod(java.lang.Object)" 20 | ) 21 | private val scalaClassesToSkip = Set( 22 | "scala.runtime.LazyRef", 23 | "scala.runtime.LazyBoolean", 24 | "scala.runtime.LazyChar", 25 | "scala.runtime.LazyShort", 26 | "scala.runtime.LazyInt", 27 | "scala.runtime.LazyLong", 28 | "scala.runtime.LazyFloat", 29 | "scala.runtime.LazyDouble", 30 | "scala.runtime.LazyUnit", 31 | "scala.runtime.BoxesRunTime" 32 | ) 33 | private val scala3ClassesToSkip = scalaClassesToSkip ++ Set("scala.runtime.LazyVals$") 34 | private val scala2ClassesToSkip = scalaClassesToSkip 35 | private val arrayWrappers = Set( 36 | "wrapRefArray(java.lang.Object[])", 37 | "wrapIntArray(int[])", 38 | "wrapDoubleArray(double[])", 39 | "wrapLongArray(long[])", 40 | "wrapFloatArray(float[])", 41 | "wrapShortArray(short[])", 42 | "wrapByteArray(byte[])", 43 | "wrapBooleanArray(boolean[])", 44 | "wrapUnitArray(scala.runtime.BoxedUnit[])" 45 | ) 46 | 47 | private val scalaMethodsToSkip = 48 | arrayWrappers.map("scala.runtime.ScalaRunTime$." + _) ++ 49 | arrayWrappers.map("scala.LowPriorityImplicits." + _) 50 | 51 | private val methodsToSkip = javaMethodsToSkip ++ scalaMethodsToSkip 52 | 53 | def apply(scalaVersion: ScalaVersion): RuntimeStepFilter = { 54 | if (scalaVersion.isScala2) 55 | new RuntimeStepFilter(scala2ClassesToSkip ++ javaClassesToSkip, methodsToSkip) 56 | else 57 | new RuntimeStepFilter(scala3ClassesToSkip ++ scala2ClassesToSkip ++ javaClassesToSkip, methodsToSkip) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3Decoder.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.stacktrace 2 | 3 | import ch.epfl.scala.debugadapter.Debuggee 4 | import ch.epfl.scala.debugadapter.Logger 5 | import ch.epfl.scala.debugadapter.internal.ByteCode 6 | import ch.epfl.scala.debugadapter.internal.ThrowOrWarn 7 | import ch.epfl.scala.debugadapter.internal.stacktrace.JdiExtensions.* 8 | import com.microsoft.java.debug.core.adapter.stacktrace.DecodedMethod 9 | import com.sun.jdi 10 | 11 | import scala.util.control.NonFatal 12 | 13 | class Scala3Decoder( 14 | debuggee: Debuggee, 15 | classLoader: ClassLoader, 16 | private var bridge: Scala3DecoderBridge, 17 | protected val logger: Logger, 18 | protected val testMode: Boolean 19 | ) extends ScalaDecoder 20 | with ThrowOrWarn { 21 | 22 | override def skipOver(method: jdi.Method): Boolean = 23 | // some trait initializers are completely empty 24 | if (method.isTraitInitializer) method.bytecodes.toSeq == Seq(ByteCode.RETURN) 25 | else decode(method).isGenerated 26 | 27 | override def decode(method: jdi.Method): DecodedMethod = 28 | try 29 | if (method.declaringType.isDynamicClass) JavaMethod(method, isGenerated = true) 30 | else if (method.isJava) 31 | JavaMethod(method, isGenerated = (method.isBridge || method.isSynthetic) && !method.isConstructor) 32 | else if (method.isStaticMain) { 33 | // Scala static main methods don't contain any debug info 34 | // also we pretend it is not generated to avoid empty stack traces 35 | JavaMethod(method, isGenerated = false) 36 | } else if (method.isStaticConstructor) { 37 | // try remove and fix binary decoder 38 | JavaMethod(method, isGenerated = false) 39 | } else bridge.decode(method) 40 | catch { 41 | case NonFatal(e) => 42 | throwOrWarn(e) 43 | JavaMethod(method, isGenerated = method.isBridge) 44 | } 45 | 46 | override def reload(): Unit = 47 | bridge = Scala3DecoderBridge(debuggee, classLoader, logger, testMode) 48 | } 49 | 50 | object Scala3Decoder { 51 | def apply(debuggee: Debuggee, classLoader: ClassLoader, logger: Logger, testMode: Boolean): Scala3Decoder = { 52 | val bridge = Scala3DecoderBridge(debuggee, classLoader, logger, testMode) 53 | new Scala3Decoder(debuggee, classLoader, bridge, logger, testMode) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3DecoderBridge.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.stacktrace 2 | 3 | import ch.epfl.scala.debugadapter.Debuggee 4 | import ch.epfl.scala.debugadapter.Java8 5 | import ch.epfl.scala.debugadapter.Java9OrAbove 6 | import ch.epfl.scala.debugadapter.Logger 7 | import ch.epfl.scala.debugadapter.internal.Errors 8 | import com.microsoft.java.debug.core.adapter.stacktrace.DecodedMethod 9 | import com.sun.jdi 10 | 11 | import java.lang.reflect.InvocationTargetException 12 | import java.lang.reflect.Method 13 | import java.nio.file.Files 14 | import java.nio.file.Path 15 | import java.util.function.Consumer 16 | import scala.jdk.CollectionConverters.* 17 | 18 | private class Scala3DecoderBridge( 19 | instance: Any, 20 | decodeMethod: Method 21 | ) { 22 | def decode(method: jdi.Method): DecodedMethod = 23 | try new DecodedMethodBridge(decodeMethod.invoke(instance, method)) 24 | catch { 25 | case e: InvocationTargetException => throw Errors.frameDecodingFailure(e.getCause) 26 | } 27 | } 28 | 29 | private object Scala3DecoderBridge { 30 | def apply(debuggee: Debuggee, classLoader: ClassLoader, logger: Logger, testMode: Boolean): Scala3DecoderBridge = { 31 | val className = "ch.epfl.scala.debugadapter.internal.Scala3DecoderBridge" 32 | val cls = classLoader.loadClass(className) 33 | val instance = newInstance(debuggee, cls, logger, testMode) 34 | val decodeMethod = cls.getMethod("decode", classOf[jdi.Method]) 35 | new Scala3DecoderBridge(instance, decodeMethod) 36 | } 37 | 38 | private def newInstance(debuggee: Debuggee, decoderClass: Class[?], logger: Logger, testMode: Boolean) = { 39 | val javaRuntimeJars = debuggee.javaRuntime.toSeq.flatMap { 40 | case Java8(_, classJars, _) => classJars 41 | case java9OrAbove: Java9OrAbove => 42 | java9OrAbove.classSystems.flatMap { javaFs => 43 | Files.list(javaFs.fileSystem.getPath("/modules")).iterator.asScala.toSeq 44 | } 45 | } 46 | val debuggeeClasspath = debuggee.classPath.toArray ++ javaRuntimeJars 47 | val warnLogger: Consumer[String] = msg => logger.warn(msg) 48 | val ctr = decoderClass.getConstructor(classOf[Array[Path]], classOf[Consumer[String]], classOf[Boolean]) 49 | 50 | ctr.newInstance(debuggeeClasspath, warnLogger, testMode: java.lang.Boolean) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/ScalaDecoder.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.stacktrace 2 | 3 | import ch.epfl.scala.debugadapter.Debuggee 4 | import ch.epfl.scala.debugadapter.Logger 5 | 6 | import ch.epfl.scala.debugadapter.internal.DebugTools 7 | import ch.epfl.scala.debugadapter.internal.ScalaExtension.* 8 | import com.sun.jdi 9 | import com.microsoft.java.debug.core.adapter.stacktrace.DecodedMethod 10 | import scala.util.Try 11 | 12 | trait ScalaDecoder extends StepFilter { 13 | def decode(method: jdi.Method): DecodedMethod 14 | def reload(): Unit 15 | } 16 | 17 | object ScalaDecoder { 18 | def apply( 19 | debuggee: Debuggee, 20 | tools: DebugTools, 21 | logger: Logger, 22 | testMode: Boolean 23 | ): ScalaDecoder = { 24 | if (debuggee.scalaVersion.isScala2) 25 | new Scala2Decoder(tools.sourceLookUp, debuggee.scalaVersion, logger, testMode) 26 | else 27 | tools.decoder 28 | .flatMap { classLoader => 29 | Try(Scala3Decoder(debuggee, classLoader, logger, testMode)) 30 | .warnFailure(logger, s"Cannot load step filter for Scala ${debuggee.scalaVersion}") 31 | } 32 | .getOrElse(new Scala2Decoder(tools.sourceLookUp, debuggee.scalaVersion, logger, testMode)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/StepFilter.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal.stacktrace 2 | 3 | import com.sun.jdi.Method 4 | import com.sun.jdi.Location 5 | 6 | trait StepFilter { 7 | def skipOver(method: Method): Boolean = false 8 | def skipOut(upperLocation: Location, method: Method): Boolean = false 9 | } 10 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/testing/TestSuiteEvent.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.testing 2 | 3 | import sbt.testing.{Event, Status} 4 | 5 | sealed trait TestSuiteEvent 6 | object TestSuiteEvent { 7 | case object Done extends TestSuiteEvent 8 | case class Error(message: String) extends TestSuiteEvent 9 | case class Warn(message: String) extends TestSuiteEvent 10 | case class Info(message: String) extends TestSuiteEvent 11 | case class Debug(message: String) extends TestSuiteEvent 12 | case class Trace(throwable: Throwable) extends TestSuiteEvent 13 | 14 | /** @param testSuite Class name of test suite */ 15 | case class Results(testSuite: String, events: List[Event]) extends TestSuiteEvent { 16 | 17 | // if no duration is available value is set to -1 18 | val duration = events.collect { 19 | case e if e.duration() > 0 => e.duration() 20 | }.sum 21 | def passed = events.count(_.status() == Status.Success) 22 | def skipped = events.count(_.status() == Status.Skipped) 23 | def failed = events.count(_.status() == Status.Failure) 24 | def canceled = events.count(_.status() == Status.Canceled) 25 | def ignored = events.count(_.status() == Status.Ignored) 26 | def pending = events.count(_.status() == Status.Pending) 27 | def errors = events.count(_.status() == Status.Error) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/testing/TestSuiteEventHandler.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.testing 2 | 3 | import sbt.testing.Status 4 | import scala.jdk.CollectionConverters.* 5 | 6 | trait TestSuiteEventHandler { 7 | def handle(testSuiteEvent: TestSuiteEvent): Unit 8 | } 9 | 10 | object TestSuiteEventHandler { 11 | def formatError( 12 | testName: String, 13 | failureMessage: String, 14 | indentSize: Int 15 | ): String = { 16 | val indent = " " * indentSize 17 | (testName, failureMessage) match { 18 | case ("", failureMessage) => s"$indent* $failureMessage" 19 | case (testName, "") => s"$indent* $testName" 20 | case (testName, failureMessage) => s"$indent* $testName - $failureMessage" 21 | } 22 | } 23 | 24 | /** 25 | * Provide a summary of test suite execution based on passed TestSuiteEvent.Results parameter. 26 | */ 27 | def summarizeResults( 28 | testSuiteResult: TestSuiteEvent.Results 29 | ): TestSuiteSummary = { 30 | val results = testSuiteResult.events.map { e => 31 | val name = TestUtils.printSelector(e.selector).getOrElse("") 32 | val testResult: SingleTestSummary = e.status() match { 33 | case Status.Success => 34 | SingleTestResult.Passed(name, e.duration) 35 | case Status.Failure => 36 | val failedMsg = 37 | TestUtils.printThrowable(e.throwable()).getOrElse("") 38 | val stackTrace = TestUtils.printStackTrace(e.throwable()).getOrElse("") 39 | val formatted = 40 | TestSuiteEventHandler.formatError( 41 | name, 42 | failedMsg, 43 | indentSize = 0 44 | ) 45 | SingleTestResult.Failed(name, e.duration, formatted, stackTrace, location = null) 46 | case _ => 47 | SingleTestResult.Skipped(name) 48 | } 49 | testResult 50 | }.asJava 51 | 52 | TestSuiteSummary( 53 | suiteName = testSuiteResult.testSuite, 54 | duration = testSuiteResult.duration, 55 | tests = results 56 | ) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/testing/TestSuiteSummary.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.testing 2 | 3 | /** 4 | * Summary of test suite execution which is being send to the dap client. 5 | * Because of gson serialization, this class uses Java List rather than a scala collection. 6 | */ 7 | final case class TestSuiteSummary( 8 | suiteName: String, 9 | duration: Long, 10 | tests: java.util.List[SingleTestSummary] 11 | ) 12 | 13 | final class TestLocation( 14 | val file: String, 15 | val line: Int 16 | ) 17 | 18 | /** 19 | * Sealed hierarchy that models 3 possible outcomes of single test case. 20 | * I wanted to model this a discriminated union type and due to gson serialization, 21 | * the best solution I was able to find at that moment was additional kind field. 22 | * Each class has a smart constructor available in the companion objects which sets value of kind correctly. 23 | */ 24 | sealed trait SingleTestSummary 25 | object SingleTestResult { 26 | final class Passed private ( 27 | val kind: String, 28 | val testName: String, 29 | val duration: Long 30 | ) extends SingleTestSummary 31 | object Passed { 32 | def apply(testName: String, duration: Long): Passed = 33 | new Passed("passed", testName, duration) 34 | } 35 | 36 | final class Skipped private ( 37 | val kind: String, 38 | val testName: String 39 | ) extends SingleTestSummary 40 | object Skipped { 41 | def apply(testName: String): Skipped = new Skipped("skipped", testName) 42 | } 43 | 44 | final class Failed private ( 45 | val kind: String, 46 | val testName: String, 47 | val duration: Long, 48 | val error: String, 49 | val stackTrace: String, 50 | val location: TestLocation 51 | ) extends SingleTestSummary 52 | object Failed { 53 | def apply( 54 | testName: String, 55 | duration: Long, 56 | error: String, 57 | stackTrace: String, 58 | location: TestLocation 59 | ): Failed = 60 | new Failed("failed", testName, duration, error, stackTrace, location) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/ch/epfl/scala/debugadapter/testing/TestUtils.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.testing 2 | 3 | import sbt.testing.* 4 | 5 | import java.io.ByteArrayOutputStream 6 | import java.io.PrintStream 7 | 8 | object TestUtils { 9 | def printSelector(selector: Selector): Option[String] = selector match { 10 | case c: TestSelector => Some(c.testName()) 11 | case c: SuiteSelector => Some(c.toString) 12 | case c: NestedSuiteSelector => Some(c.suiteId()) 13 | case c: TestWildcardSelector => Some(c.testWildcard()) 14 | case c: NestedTestSelector => Some(c.testName()) 15 | case _ => None 16 | } 17 | 18 | def printThrowable(opt: OptionalThrowable): Option[String] = { 19 | if (opt.isEmpty) None 20 | else Some(stripTestFrameworkSpecificInformation(opt.get().getMessage)) 21 | } 22 | 23 | def printStackTrace(opt: OptionalThrowable): Option[String] = { 24 | if (opt.isEmpty) None 25 | else 26 | Some { 27 | val writer = new StringBuffer 28 | val outputStream = new ByteArrayOutputStream() 29 | val printStream = new PrintStream(outputStream) 30 | opt.get().printStackTrace(printStream) 31 | outputStream.toString 32 | } 33 | } 34 | 35 | private val specs2Prefix = "java.lang.Exception: " 36 | private val utestPrefix = "utest.AssertionError: " 37 | private val scalaTestPrefix = "org.scalatest.exceptions.TestFailedException: " 38 | 39 | def stripTestFrameworkSpecificInformation(message: String): String = 40 | if (message.startsWith(scalaTestPrefix)) 41 | message.drop(scalaTestPrefix.length) 42 | else if (message.startsWith(specs2Prefix)) message.drop(specs2Prefix.length) 43 | else if (message.startsWith(utestPrefix)) message.drop(utestPrefix.length) 44 | else message 45 | } 46 | -------------------------------------------------------------------------------- /modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/DecodedMethodBridge.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import ch.epfl.scala.decoder.DecodedMethod 4 | import ch.epfl.scala.decoder.StackTraceFormatter 5 | import tastyquery.Symbols.* 6 | import tastyquery.Modifiers.* 7 | 8 | class DecodedMethodBridge(method: DecodedMethod, formatter: StackTraceFormatter): 9 | def format: String = formatter.format(method) 10 | def isGenerated: Boolean = method.isGenerated 11 | 12 | extension (method: DecodedMethod) 13 | private def isGenerated: Boolean = 14 | method match 15 | case method: DecodedMethod.ValOrDefDef => 16 | val sym = method.symbol 17 | (sym.isGetter && (!sym.owner.isTrait || !sym.isModuleOrLazyVal)) || // getter 18 | (sym.isLocal && sym.isModuleOrLazyVal) || // local def 19 | sym.isSetter || 20 | (sym.isSynthetic && !sym.isLocal) || 21 | sym.isExport 22 | case method: DecodedMethod.LazyInit => method.symbol.owner.isTrait 23 | case _: DecodedMethod.TraitStaticForwarder => true 24 | case _: DecodedMethod.TraitParamAccessor => true 25 | case _: DecodedMethod.MixinForwarder => true 26 | case _: DecodedMethod.Bridge => true 27 | case _: DecodedMethod.StaticForwarder => true 28 | case _: DecodedMethod.OuterAccessor => true 29 | case _: DecodedMethod.SetterAccessor => true 30 | case _: DecodedMethod.GetterAccessor => true 31 | case _: DecodedMethod.SuperAccessor => true 32 | case _: DecodedMethod.SpecializedMethod => true 33 | case _: DecodedMethod.InlineAccessor => true 34 | case _: DecodedMethod.AdaptedFun => true 35 | case _: DecodedMethod.SAMOrPartialFunctionConstructor => true 36 | case method: DecodedMethod.InlinedMethod => method.underlying.isGenerated 37 | case _ => false 38 | 39 | extension (symbol: TermSymbol) 40 | private def isGetter = !symbol.isMethod 41 | private def isModuleOrLazyVal: Boolean = symbol.isLazyVal || symbol.isModuleVal 42 | private def isLazyVal: Boolean = symbol.kind == TermSymbolKind.LazyVal 43 | 44 | extension (symbol: Symbol) 45 | private def isTrait = symbol.isClass && symbol.asClass.isTrait 46 | private def isLocal = symbol.owner.isTerm 47 | -------------------------------------------------------------------------------- /modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/Scala3DecoderBridge.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import ch.epfl.scala.decoder.binary 4 | 5 | import java.nio.file.Path 6 | import java.util.function.Consumer 7 | import ch.epfl.scala.decoder.* 8 | import ch.epfl.scala.decoder.jdi.JdiMethod 9 | 10 | class Scala3DecoderBridge( 11 | classEntries: Array[Path], 12 | warnLogger: Consumer[String], 13 | testMode: Boolean 14 | ): 15 | private val decoder: BinaryDecoder = BinaryDecoder.cached(classEntries)( 16 | // make it quiet, or it would be too verbose when things go wrong 17 | using ThrowOrWarn(_ => (), testMode) 18 | ) 19 | private val formatter = StackTraceFormatter(using ThrowOrWarn(s => warnLogger.accept(s), testMode)) 20 | 21 | def decode(obj: com.sun.jdi.Method): DecodedMethodBridge = 22 | new DecodedMethodBridge(decoder.decode(JdiMethod(obj)), formatter) 23 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-2/scala/tools/nsc/ExpressionCompilerBridge.scala: -------------------------------------------------------------------------------- 1 | package scala.tools.nsc 2 | 3 | import java.nio.file.Path 4 | import java.util.function.Consumer 5 | import java.{util => ju} 6 | import scala.jdk.CollectionConverters._ 7 | import scala.tools.nsc.evaluation.ExpressionGlobal 8 | import scala.util.control.NonFatal 9 | 10 | final class ExpressionCompilerBridge { 11 | def run( 12 | outDir: Path, 13 | expressionClassName: String, 14 | classPath: String, 15 | options: Array[String], 16 | sourceFile: Path, 17 | line: Int, 18 | expression: String, 19 | localVariables: ju.Set[String], 20 | pckg: String, 21 | errorConsumer: Consumer[String], 22 | testMode: Boolean 23 | ): Boolean = { 24 | val args = List( 25 | "-d", 26 | outDir.toString, 27 | "-classpath", 28 | classPath 29 | // Debugging: Print the tree after phases of the debugger 30 | // "-Xprint:extract-expression,resolve-reflect-eval", 31 | // "-Vdebug" 32 | ) ++ options :+ sourceFile.toString 33 | 34 | val command = new CompilerCommand(args, errorConsumer.accept(_)) 35 | val reporter = new ExpressionReporter(errorConsumer.accept, command.settings) 36 | val global = new ExpressionGlobal( 37 | command.settings, 38 | reporter, 39 | expressionClassName, 40 | line, 41 | expression, 42 | localVariables.asScala.toSet, 43 | pckg, 44 | testMode 45 | ) 46 | 47 | try { 48 | val run = new global.Run() 49 | run.compile(List(sourceFile.toString)) 50 | !reporter.hasErrors 51 | } catch { 52 | case NonFatal(t) => 53 | t.printStackTrace() 54 | errorConsumer.accept(t.getMessage()) 55 | false 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-2/scala/tools/nsc/ExpressionReporter.scala: -------------------------------------------------------------------------------- 1 | package scala.tools.nsc 2 | 3 | import scala.tools.nsc.reporters.FilteringReporter 4 | import scala.reflect.internal.util.Position 5 | import scala.reflect.internal.Reporter 6 | import scala.annotation.nowarn 7 | 8 | class ExpressionReporter(reportError: String => Unit, val settings: Settings) extends FilteringReporter { 9 | 10 | @nowarn 11 | override def doReport(pos: Position, msg: String, severity: Severity): Unit = { 12 | severity match { 13 | case Reporter.ERROR => 14 | val newPos = pos.source.positionInUltimateSource(pos) 15 | val formatted = Position.formatMessage(newPos, s"${clabel(severity)}$msg", shortenFile = false) 16 | reportError(formatted) 17 | case _ => 18 | // TODO report the warnings 19 | () 20 | } 21 | } 22 | 23 | private def clabel(severity: Severity): String = severity match { 24 | case Reporter.ERROR => "error: " 25 | case Reporter.WARNING => "warning: " 26 | case _ => "" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-2/scala/tools/nsc/evaluation/JavaEncoding.scala: -------------------------------------------------------------------------------- 1 | package scala.tools.nsc.evaluation 2 | 3 | import scala.tools.nsc.SubComponent 4 | 5 | /** Encoding of symbol names for the IR. */ 6 | trait JavaEncoding { self: SubComponent => 7 | import global._ 8 | 9 | def encode(tpe: Type): String = 10 | tpe.dealiasWiden match { 11 | case ArrayTypeRef(el) => s"[${binaryName(el)}" 12 | case TypeRef(_, sym, _) => encode(sym.asType) 13 | case AnnotatedType(_, t) => encode(t) 14 | } 15 | 16 | def encode(sym: TypeSymbol): String = { 17 | /* When compiling Array.scala, the type parameter T is not erased and shows up in method 18 | * signatures, e.g. `def apply(i: Int): T`. A TypeRef to T is replaced by ObjectReference. 19 | */ 20 | if (!sym.isClass) "java.lang.Object" 21 | else if (sym.isPrimitiveValueClass) primitiveName(sym) 22 | else className(sym) 23 | } 24 | 25 | private def binaryName(tpe: Type): String = 26 | tpe match { 27 | case ArrayTypeRef(el) => s"[${binaryName(el)}" 28 | case TypeRef(_, sym, _) => 29 | if (sym.isPrimitiveValueClass) primitiveBinaryName(sym) 30 | else classBinaryName(sym) 31 | case AnnotatedType(_, t) => binaryName(t) 32 | } 33 | 34 | private def primitiveName(sym: Symbol): String = 35 | if (sym == definitions.UnitClass) "void" 36 | else if (sym == definitions.BooleanClass) "boolean" 37 | else if (sym == definitions.CharClass) "char" 38 | else if (sym == definitions.ByteClass) "byte" 39 | else if (sym == definitions.ShortClass) "short" 40 | else if (sym == definitions.IntClass) "int" 41 | else if (sym == definitions.LongClass) "long" 42 | else if (sym == definitions.FloatClass) "float" 43 | else if (sym == definitions.DoubleClass) "double" 44 | else throw new Exception(s"Unknown primitive value class $sym") 45 | 46 | private def primitiveBinaryName(sym: Symbol): String = 47 | if (sym == definitions.BooleanClass) "Z" 48 | else if (sym == definitions.CharClass) "C" 49 | else if (sym == definitions.ByteClass) "B" 50 | else if (sym == definitions.ShortClass) "S" 51 | else if (sym == definitions.IntClass) "I" 52 | else if (sym == definitions.LongClass) "J" 53 | else if (sym == definitions.FloatClass) "F" 54 | else if (sym == definitions.DoubleClass) "D" 55 | else throw new Exception(s"Unknown primitive value class $sym") 56 | 57 | private def className(sym: Symbol): String = { 58 | val sym1 = 59 | if (sym.isModuleClass && sym.isJavaDefined) sym.linkedClassOfClass 60 | else sym 61 | 62 | /* Some rewirings: 63 | * - scala.Nothing to scala.runtime.Nothing$. 64 | * - scala.Null to scala.runtime.Null$. 65 | */ 66 | if (sym1 == definitions.NothingClass) "scala.runtime.Nothing$" 67 | else if (sym1 == definitions.NullClass) "scala.runtime.Null$" 68 | else sym1.javaClassName 69 | } 70 | 71 | private def classBinaryName(sym: Symbol): String = 72 | s"L${className(sym)};" 73 | } 74 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3.0/dotty/tools/dotc/ExpressionCompiler.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc 2 | 3 | import dotty.tools.dotc.core.Contexts.Context 4 | import dotty.tools.dotc.core.Phases.Phase 5 | import dotty.tools.dotc.evaluation.* 6 | import dotty.tools.dotc.transform.ElimByName 7 | import dotty.tools.dotc.util.SourceFile 8 | 9 | class ExpressionCompiler(using ExpressionContext)(using Context) extends Compiler: 10 | 11 | override protected def frontendPhases: List[List[Phase]] = 12 | List(EvaluationFrontEnd()) :: super.frontendPhases.tail 13 | 14 | override protected def transformPhases: List[List[Phase]] = 15 | // the ExtractExpression phase should be after ElimByName and ExtensionMethods, 16 | // and before LambdaLift 17 | val transformPhases = super.transformPhases 18 | val index = transformPhases.indexWhere(_.exists(_.phaseName == ElimByName.name)) 19 | val (before, after) = transformPhases.splitAt(index + 1) 20 | (before :+ List(ExtractExpression())) ++ (after :+ List(ResolveReflectEval())) 21 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3.0/dotty/tools/dotc/ExpressionReporter.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc 2 | 3 | import dotty.tools.dotc.core.Contexts.* 4 | import dotty.tools.dotc.reporting.AbstractReporter 5 | import dotty.tools.dotc.reporting.Diagnostic 6 | 7 | class ExpressionReporter(reportError: String => Unit) extends AbstractReporter: 8 | override def doReport(dia: Diagnostic)(using Context): Unit = 9 | // println(messageAndPos(dia)) 10 | dia match 11 | case error: Diagnostic.Error => 12 | val newPos = error.pos.source.positionInUltimateSource(error.pos) 13 | val level = diagnosticLevel(error) 14 | reportError(stripColor(messageAndPos(error.msg, newPos, level))) 15 | case _ => 16 | // TODO report the warnings 17 | () 18 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3.0/dotty/tools/dotc/evaluation/ExpressionFrontEnd.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc.evaluation 2 | 3 | import dotty.tools.dotc.CompilationUnit 4 | import dotty.tools.dotc.ExpressionContext 5 | import dotty.tools.dotc.ast 6 | import dotty.tools.dotc.core.Contexts.Context 7 | import dotty.tools.dotc.core.Contexts.ctx 8 | import dotty.tools.dotc.core.Symbols.defn 9 | import dotty.tools.dotc.report 10 | import dotty.tools.dotc.typer.FrontEnd 11 | import dotty.tools.dotc.typer.ImportInfo.withRootImports 12 | import dotty.tools.dotc.util.NoSourcePosition 13 | import dotty.tools.dotc.util.SourcePosition 14 | import dotty.tools.dotc.util.Stats.record 15 | 16 | class EvaluationFrontEnd(using ExpressionContext)(using Context) extends FrontEnd: 17 | private val insertExpression = InsertExpression() 18 | 19 | private var remaining: List[Context] = Nil 20 | private var firstXmlPos: SourcePosition = NoSourcePosition 21 | 22 | override def runOn(units: List[CompilationUnit])(using 23 | Context 24 | ): List[CompilationUnit] = 25 | val unitContexts = 26 | for unit <- units yield 27 | report.inform(s"compiling ${unit.source}") 28 | ctx.fresh.setCompilationUnit(unit).withRootImports 29 | unitContexts.foreach(parse(using _)) 30 | record("parsedTrees", ast.Trees.ntrees) 31 | 32 | unitContexts.foreach(insertExpression.run(using _)) 33 | 34 | remaining = unitContexts 35 | while remaining.nonEmpty do 36 | enterSyms(using remaining.head) 37 | remaining = remaining.tail 38 | 39 | if firstXmlPos.exists && !defn.ScalaXmlPackageClass.exists then 40 | report.error( 41 | """To support XML literals, your project must depend on scala-xml. 42 | |See https://github.com/scala/scala-xml for more information.""".stripMargin, 43 | firstXmlPos 44 | ) 45 | 46 | unitContexts.foreach(typeCheck(using _)) 47 | record("total trees after typer", ast.Trees.ntrees) 48 | unitContexts.foreach( 49 | javaCheck(using _) 50 | ) // after typechecking to avoid cycles 51 | 52 | val newUnits = 53 | unitContexts.map(_.compilationUnit).filterNot(discardAfterTyper) 54 | ctx.run.checkSuspendedUnits(newUnits) 55 | newUnits 56 | end EvaluationFrontEnd 57 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3.0/dotty/tools/dotc/evaluation/SymUtils.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc.evaluation 2 | 3 | import dotty.tools.dotc.core.Contexts.Context 4 | import dotty.tools.dotc.core.Symbols.Symbol 5 | 6 | object SymUtils: 7 | export dotty.tools.dotc.transform.SymUtils.{isLocal => _, enclosingMethodOrClass => _, *} 8 | 9 | extension (self: Symbol) def isLocal(using Context) = dotty.tools.dotc.transform.SymUtils.isLocal(self) 10 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3.1+/dotty/tools/dotc/ExpressionCompiler.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc 2 | 3 | import dotty.tools.dotc.core.Contexts.Context 4 | import dotty.tools.dotc.core.Phases.Phase 5 | import dotty.tools.dotc.evaluation.* 6 | import dotty.tools.dotc.transform.ElimByName 7 | 8 | class ExpressionCompiler(using ExpressionContext)(using Context) extends Compiler: 9 | 10 | override protected def frontendPhases: List[List[Phase]] = 11 | val parser :: others = super.frontendPhases: @unchecked 12 | parser :: List(InsertExpression()) :: others 13 | 14 | override protected def transformPhases: List[List[Phase]] = 15 | // the ExtractExpression phase should be after ElimByName and ExtensionMethods, 16 | // and before LambdaLift 17 | val transformPhases = super.transformPhases 18 | val index = transformPhases.indexWhere(_.exists(_.phaseName == ElimByName.name)) 19 | val (before, after) = transformPhases.splitAt(index + 1) 20 | (before :+ List(ExtractExpression())) ++ (after :+ List(ResolveReflectEval())) 21 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3.1+/dotty/tools/dotc/ExpressionReporter.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc 2 | 3 | import dotty.tools.dotc.core.Contexts.* 4 | import dotty.tools.dotc.reporting.AbstractReporter 5 | import dotty.tools.dotc.reporting.Diagnostic 6 | import scala.util.matching.Regex 7 | 8 | class ExpressionReporter(reportError: String => Unit) extends AbstractReporter: 9 | override def doReport(dia: Diagnostic)(using Context): Unit = 10 | // When printing trees, we trim what comes after 'def evaluate' 11 | // val message = messageAndPos(dia) 12 | // val shortMessage = message.split(Regex.quote("\u001b[33mdef\u001b[0m \u001b[36mgetLocalValue\u001b[0m"))(0) 13 | // println(shortMessage) 14 | dia match 15 | case error: Diagnostic.Error => 16 | val newPos = error.pos.source.positionInUltimateSource(error.pos) 17 | val errorWithNewPos = new Diagnostic.Error(error.msg, newPos) 18 | reportError(stripColor(messageAndPos(errorWithNewPos))) 19 | case _ => 20 | // TODO report the warnings 21 | () 22 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3.1.0-3.3.3/dotty/tools/dotc/evaluation/SymUtils.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc.evaluation 2 | 3 | import dotty.tools.dotc.core.Contexts.Context 4 | import dotty.tools.dotc.core.Symbols.Symbol 5 | 6 | object SymUtils: 7 | export dotty.tools.dotc.transform.SymUtils.{isLocal => _, enclosingMethodOrClass => _, *} 8 | 9 | extension (self: Symbol) def isLocal(using Context) = dotty.tools.dotc.transform.SymUtils.isLocal(self) 10 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3.3.4+/dotty/tools/dotc/evaluation/SymUtils.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc.evaluation 2 | 3 | object SymUtils extends dotty.tools.dotc.core.SymUtils 4 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3.4+/dotty/tools/dotc/ExpressionCompiler.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc 2 | 3 | import dotty.tools.dotc.core.Contexts.Context 4 | import dotty.tools.dotc.core.Phases.Phase 5 | import dotty.tools.dotc.evaluation.* 6 | import dotty.tools.dotc.transform.ElimByName 7 | 8 | class ExpressionCompiler(using ExpressionContext)(using Context) extends Compiler: 9 | 10 | override protected def frontendPhases: List[List[Phase]] = 11 | val parser :: others = super.frontendPhases: @unchecked 12 | parser :: List(InsertExpression()) :: others 13 | 14 | override protected def transformPhases: List[List[Phase]] = 15 | // the ExtractExpression phase should be after ElimByName and ExtensionMethods, 16 | // and before LambdaLift 17 | val transformPhases = super.transformPhases 18 | val index = transformPhases.indexWhere(_.exists(_.phaseName == ElimByName.name)) 19 | val (before, after) = transformPhases.splitAt(index + 1) 20 | (before :+ List(ExtractExpression())) ++ (after :+ List(ResolveReflectEval())) 21 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3.4+/dotty/tools/dotc/ExpressionReporter.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc 2 | 3 | import dotty.tools.dotc.core.Contexts.* 4 | import dotty.tools.dotc.reporting.AbstractReporter 5 | import dotty.tools.dotc.reporting.Diagnostic 6 | 7 | class ExpressionReporter(reportError: String => Unit) extends AbstractReporter: 8 | override def doReport(dia: Diagnostic)(using Context): Unit = 9 | // println(messageAndPos(dia)) 10 | dia match 11 | case error: Diagnostic.Error => 12 | val newPos = error.pos.source.positionInUltimateSource(error.pos) 13 | val errorWithNewPos = new Diagnostic.Error(error.msg, newPos) 14 | reportError(stripColor(messageAndPos(errorWithNewPos))) 15 | case _ => 16 | // TODO report the warnings 17 | () 18 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3/dotty/tools/dotc/ExpressionCompilerBridge.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc 2 | 3 | import java.nio.file.Path 4 | import java.util.function.Consumer 5 | import java.{util => ju} 6 | import scala.jdk.CollectionConverters.* 7 | import scala.util.control.NonFatal 8 | import dotty.tools.dotc.reporting.StoreReporter 9 | import dotty.tools.dotc.core.Contexts.Context 10 | 11 | class ExpressionCompilerBridge: 12 | def run( 13 | outDir: Path, 14 | expressionClassName: String, 15 | classPath: String, 16 | options: Array[String], 17 | sourceFile: Path, 18 | line: Int, 19 | expression: String, 20 | localVariables: ju.Set[String], 21 | pckg: String, 22 | errorConsumer: Consumer[String], 23 | testMode: Boolean 24 | ): Boolean = 25 | val args = Array( 26 | "-d", 27 | outDir.toString, 28 | "-classpath", 29 | classPath, 30 | "-Yskip:pureStats" 31 | // Debugging: Print the tree after phases of the debugger 32 | // "-Vprint:insert-expression,resolve-reflect-eval", 33 | ) ++ options :+ sourceFile.toString 34 | val exprCtx = 35 | ExpressionContext(expressionClassName, line, expression, localVariables.asScala.toSet, pckg, testMode) 36 | 37 | val driver = new Driver: 38 | protected override def newCompiler(using Context): ExpressionCompiler = ExpressionCompiler(using exprCtx) 39 | val reporter = ExpressionReporter(error => errorConsumer.accept(error)) 40 | try 41 | driver.process(args, reporter) 42 | !reporter.hasErrors 43 | catch 44 | case NonFatal(cause) => 45 | cause.printStackTrace() 46 | throw cause 47 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3/dotty/tools/dotc/ExpressionContext.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc 2 | 3 | import dotty.tools.dotc.ast.tpd.* 4 | import dotty.tools.dotc.core.Symbols.* 5 | import dotty.tools.dotc.core.Types.* 6 | import dotty.tools.dotc.core.Names.* 7 | import dotty.tools.dotc.core.Flags.* 8 | import dotty.tools.dotc.core.Contexts.* 9 | import dotty.tools.dotc.evaluation.SymUtils.* 10 | 11 | class ExpressionContext( 12 | uniqueName: String, 13 | val breakpointLine: Int, 14 | val expression: String, 15 | val localVariables: Set[String], 16 | val pckg: String, 17 | val testMode: Boolean 18 | ): 19 | val expressionTermName: TermName = termName(uniqueName.toLowerCase.toString) 20 | val expressionClassName: TypeName = typeName(uniqueName) 21 | 22 | var expressionSymbol: TermSymbol = null 23 | // all classes and def in the chain of owners of the expression from local to global 24 | // we store them to resolve the captured variables 25 | var classOwners: Seq[ClassSymbol] = null 26 | var capturingMethod: Option[TermSymbol] = None 27 | 28 | def store(exprSym: Symbol)(using Context): Unit = 29 | expressionSymbol = exprSym.asTerm 30 | classOwners = exprSym.ownersIterator.collect { case cls: ClassSymbol => cls }.toSeq 31 | capturingMethod = exprSym.ownersIterator 32 | .find(sym => (sym.isClass || sym.is(Method)) && sym.enclosure.is(Method)) // the first local class or method 33 | .collect { case sym if sym.is(Method) => sym.asTerm } // if it is a method 34 | 35 | def expressionClass(using Context): ClassSymbol = 36 | if pckg.isEmpty then requiredClass(expressionClassName) 37 | else requiredClass(s"$pckg.$expressionClassName") 38 | 39 | def evaluateMethod(using Context): Symbol = 40 | expressionClass.info.decl(termName("evaluate")).symbol 41 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3/dotty/tools/dotc/evaluation/EvaluationStrategy.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc.evaluation 2 | 3 | import dotty.tools.dotc.core.Symbols.* 4 | import dotty.tools.dotc.util.Property.* 5 | 6 | /** 7 | * The [[ExtractExpression]] phase attaches an [[EvaluationStrategy]] to each `reflectEval` node 8 | * to store information about the term that must be evaluated 9 | * Later, the [[ResolveReflectEval]] phase transforms each evaluation strategy into a call of 10 | * a method of the evaluation class. 11 | */ 12 | enum EvaluationStrategy: 13 | case This(cls: ClassSymbol) 14 | case Outer(outerCls: ClassSymbol) 15 | case LocalOuter(outerCls: ClassSymbol) // the $outer param in a constructor 16 | case LocalValue(variable: TermSymbol, isByName: Boolean) 17 | case LocalValueAssign(variable: TermSymbol) 18 | case MethodCapture(variable: TermSymbol, method: TermSymbol, isByName: Boolean) 19 | case MethodCaptureAssign(variable: TermSymbol, method: TermSymbol) 20 | case ClassCapture(variable: TermSymbol, cls: ClassSymbol, isByName: Boolean) 21 | case ClassCaptureAssign(variable: TermSymbol, cls: ClassSymbol) 22 | case StaticObject(obj: ClassSymbol) 23 | case Field(field: TermSymbol, isByName: Boolean) 24 | case FieldAssign(field: TermSymbol) 25 | case MethodCall(method: TermSymbol) 26 | case ConstructorCall(ctr: TermSymbol, cls: ClassSymbol) 27 | 28 | object EvaluationStrategy extends StickyKey[EvaluationStrategy] 29 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/main/scala-3/dotty/tools/dotc/evaluation/JavaEncoding.scala: -------------------------------------------------------------------------------- 1 | package dotty.tools.dotc.evaluation 2 | 3 | import dotty.tools.dotc.core.Types.* 4 | import dotty.tools.dotc.core.Contexts.* 5 | import dotty.tools.dotc.core.Symbols.* 6 | import dotty.tools.dotc.core.Flags.* 7 | import dotty.tools.dotc.core.Names.* 8 | import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions 9 | import dotty.tools.dotc.util.NameTransformer 10 | 11 | // Inspired by https://github.com/lampepfl/dotty/blob/main/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala 12 | object JavaEncoding: 13 | def encode(tpe: Type)(using Context): String = 14 | tpe.widenDealias match 15 | // Array type such as Array[Int] (kept by erasure) 16 | case JavaArrayType(el) => s"[${binaryName(el)}" 17 | case tpe: TypeRef => encode(tpe.symbol.asType) 18 | case AnnotatedType(t, _) => encode(t) 19 | 20 | def encode(sym: TypeSymbol)(using Context): String = 21 | /* When compiling Array.scala, the type parameter T is not erased and shows up in method 22 | * signatures, e.g. `def apply(i: Int): T`. A TypeRef to T is replaced by ObjectReference. 23 | */ 24 | if !sym.isClass then "java.lang.Object" 25 | else if sym.isPrimitiveValueClass then primitiveName(sym) 26 | else className(sym) 27 | 28 | def encode(name: TermName)(using Context): String = 29 | NameTransformer.encode(name.toSimpleName).toString 30 | 31 | private def binaryName(tpe: Type)(using Context): String = 32 | tpe match 33 | case JavaArrayType(el) => s"[${binaryName(el)}" 34 | case tpe: TypeRef => 35 | if tpe.symbol.isPrimitiveValueClass then primitiveBinaryName(tpe.symbol) 36 | else classBinaryName(tpe.symbol) 37 | case AnnotatedType(t, _) => binaryName(t) 38 | 39 | private def primitiveName(sym: Symbol)(using Context): String = 40 | if sym == defn.UnitClass then "void" 41 | else if sym == defn.BooleanClass then "boolean" 42 | else if sym == defn.CharClass then "char" 43 | else if sym == defn.ByteClass then "byte" 44 | else if sym == defn.ShortClass then "short" 45 | else if sym == defn.IntClass then "int" 46 | else if sym == defn.LongClass then "long" 47 | else if sym == defn.FloatClass then "float" 48 | else if sym == defn.DoubleClass then "double" 49 | else throw new Exception(s"Unknown primitive value class $sym") 50 | 51 | private def primitiveBinaryName(sym: Symbol)(using Context): String = 52 | if sym == defn.BooleanClass then "Z" 53 | else if sym == defn.CharClass then "C" 54 | else if sym == defn.ByteClass then "B" 55 | else if sym == defn.ShortClass then "S" 56 | else if sym == defn.IntClass then "I" 57 | else if sym == defn.LongClass then "J" 58 | else if sym == defn.FloatClass then "F" 59 | else if sym == defn.DoubleClass then "D" 60 | else throw new Exception(s"Unknown primitive value class $sym") 61 | 62 | private def className(sym: Symbol)(using Context): String = 63 | val sym1 = 64 | if (sym.isAllOf(ModuleClass | JavaDefined)) sym.linkedClass 65 | else sym 66 | 67 | /* Some rewirings: 68 | * - scala.Nothing to scala.runtime.Nothing$. 69 | * - scala.Null to scala.runtime.Null$. 70 | */ 71 | if sym1 == defn.NothingClass then "scala.runtime.Nothing$" 72 | else if sym1 == defn.NullClass then "scala.runtime.Null$" 73 | else sym1.javaClassName 74 | 75 | private def classBinaryName(sym: Symbol)(using Context): String = 76 | s"L${className(sym)};" 77 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/test/scala-2/ch/epfl/scala/debugadapter/ExpressionCompilerDebug.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | import ch.epfl.scala.debugadapter.testfmk.TestingDebuggee 4 | import scala.tools.nsc.ExpressionCompilerBridge 5 | 6 | import java.nio.file.Files 7 | import scala.collection.mutable.Buffer 8 | import scala.concurrent.duration._ 9 | import scala.jdk.CollectionConverters._ 10 | 11 | /** 12 | * This class is used to enter the expression compiler with a debugger 13 | * It is not meant to be run in the CI 14 | */ 15 | class ExpressionCompilerDebug extends munit.FunSuite { 16 | val scalaVersion = ScalaVersion.`2.12` 17 | val compiler = new ExpressionCompilerBridge 18 | 19 | override def munitTimeout: Duration = 1.hour 20 | 21 | test("debug test".ignore) { 22 | val source = 23 | """|package example 24 | | 25 | |object Main { 26 | | def main(args: Array[String]): Unit = { 27 | | val list = List(1) 28 | | for { 29 | | x <- list 30 | | y <- list 31 | | z = x + y 32 | | } yield x 33 | | for { 34 | | x <- list 35 | | if x == 1 36 | | } yield x 37 | | for (x <- list) yield x 38 | | for (x <- list) println(x) 39 | | } 40 | |} 41 | |""".stripMargin 42 | implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) 43 | evaluate(13, "x", Set("x")) 44 | } 45 | 46 | private def evaluate(line: Int, expression: String, localVariables: Set[String] = Set.empty)(implicit 47 | debuggee: TestingDebuggee 48 | ): Unit = { 49 | val out = debuggee.tempDir.resolve("expr-classes") 50 | if (Files.notExists(out)) Files.createDirectory(out) 51 | val errors = Buffer.empty[String] 52 | compiler.run( 53 | out, 54 | "Expression", 55 | debuggee.classPathString, 56 | debuggee.mainModule.scalacOptions.toArray, 57 | debuggee.mainSource, 58 | line, 59 | expression, 60 | localVariables.asJava, 61 | "example", 62 | error => { 63 | println(Console.RED + error + Console.RESET) 64 | errors += error 65 | }, 66 | testMode = true 67 | ) 68 | if (errors.nonEmpty) throw new Exception("Evaluation failed:\n" + errors.mkString("\n")) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /modules/expression-compiler/src/test/scala-3.3/ch/epfl/scala/debugadapter/ExpressionCompilerDebug.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | import ch.epfl.scala.debugadapter.testfmk.TestingDebuggee 4 | import dotty.tools.dotc.ExpressionCompilerBridge 5 | 6 | import java.nio.file.Files 7 | import scala.collection.mutable.Buffer 8 | import scala.concurrent.duration.* 9 | import scala.jdk.CollectionConverters.* 10 | 11 | /** 12 | * This class is used to enter the expression compiler with a debugger 13 | * It is not meant to be run in the CI 14 | */ 15 | class ExpressionCompilerDebug extends munit.FunSuite: 16 | val scalaVersion = ScalaVersion.`3.1+` 17 | val compiler = new ExpressionCompilerBridge 18 | 19 | override def munitTimeout: Duration = 1.hour 20 | 21 | test("debug test".ignore) { 22 | val source = 23 | """|package example 24 | | 25 | |object A { 26 | | def main(args: Array[String]): Unit = { 27 | | println("Hello, World!") 28 | | } 29 | | 30 | | val a1 = "a1" 31 | | private val a2 = "a2" 32 | | private[this] val a3 = "a3" 33 | | private[example] val a4 = "a4" 34 | | 35 | | override def toString: String = 36 | | a2 + a3 37 | | 38 | | object B { 39 | | val b1 = "b1" 40 | | private val b2 = "b2" 41 | | private[A] val b3 = "b3" 42 | | private[example] val b4 = "b4" 43 | | } 44 | | 45 | | private object C 46 | | private[this] object D 47 | | private[example] object E 48 | |} 49 | | 50 | |object F { 51 | | val f1 = "f1" 52 | | private[example] val f2 = "f2" 53 | | 54 | | object G 55 | | private[example] object H 56 | |} 57 | |""".stripMargin 58 | implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) 59 | evaluate(5, "a2") 60 | } 61 | 62 | private def evaluate(line: Int, expression: String, localVariables: Set[String] = Set.empty)(using 63 | debuggee: TestingDebuggee 64 | ): Unit = 65 | val out = debuggee.tempDir.resolve("expr-classes") 66 | if Files.notExists(out) then Files.createDirectory(out) 67 | val errors = Buffer.empty[String] 68 | compiler.run( 69 | out, 70 | "Expression", 71 | debuggee.classPathString, 72 | debuggee.mainModule.scalacOptions.toArray ++ Array("-Xprint:resolve-reflect-eval"), 73 | debuggee.mainSource, 74 | line, 75 | expression, 76 | localVariables.asJava, 77 | "example", 78 | error => { 79 | println(Console.RED + error + Console.RESET) 80 | errors += error 81 | }, 82 | testMode = true 83 | ) 84 | if errors.nonEmpty then throw new Exception("Evaluation failed") 85 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/main/contraband/dap.contra: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.sbtplugin.internal 2 | @target(Scala) 3 | @codecPackage("ch.epfl.scala.debugadapter.sbtplugin.internal") 4 | @fullCodec("JsonProtocol") 5 | 6 | type DebugSessionAddress { 7 | ## The target's Uri 8 | uri: java.net.URI! 9 | } 10 | 11 | type DebugSessionParams { 12 | ## A sequence of build targets affected by the debugging action. 13 | targets: [sbt.internal.bsp.BuildTargetIdentifier] 14 | 15 | ## The kind of data to expect in the `data` field. 16 | dataKind: String 17 | 18 | ## A language-agnostic JSON object interpreted by the server. 19 | data: sjsonnew.shaded.scalajson.ast.unsafe.JValue 20 | } 21 | 22 | type ScalaMainClass { 23 | ## The main class to run. 24 | class: String! 25 | 26 | ## The user arguments to the main entrypoint. 27 | arguments: [String] 28 | 29 | ## The jvm options for the application. 30 | jvmOptions: [String] 31 | 32 | ## Additional environment variables for the application. 33 | environmentVariables: [String] 34 | } 35 | 36 | type ScalaTestSuites { 37 | ## Test classes selected to be run. 38 | suites: [ch.epfl.scala.debugadapter.sbtplugin.internal.ScalaTestSuiteSelection] 39 | 40 | ## Additional jvmOptions which will be passed to the forked JVM. 41 | jvmOptions: [String] 42 | 43 | ## Environment variables should be an array of strings in format KEY=VALUE 44 | environmentVariables: [String] 45 | } 46 | 47 | type ScalaTestSuiteSelection { 48 | ## Fully qualified name of the test suite class 49 | className: String! 50 | 51 | ## List of tests which should be run within this test suite. 52 | ## Empty collection means that all of them are supposed to be executed. 53 | tests: [String] 54 | } 55 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/main/java/ch/epfl/scala/debugadapter/sbtplugin/AnnotatedFingerscan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * sbt 3 | * Copyright 2011 - 2018, Lightbend, Inc. 4 | * Copyright 2008 - 2010, Mark Harrah 5 | * Licensed under Apache License 2.0 (see LICENSE) 6 | */ 7 | package ch.epfl.scala.debugadapter.sbtplugin; 8 | 9 | import sbt.testing.AnnotatedFingerprint; 10 | 11 | import java.io.Serializable; 12 | 13 | public final class AnnotatedFingerscan implements AnnotatedFingerprint, Serializable { 14 | private final boolean isModule; 15 | private final String annotationName; 16 | 17 | public AnnotatedFingerscan(final AnnotatedFingerprint print) { 18 | isModule = print.isModule(); 19 | annotationName = print.annotationName(); 20 | } 21 | 22 | public boolean isModule() { 23 | return isModule; 24 | } 25 | 26 | public String annotationName() { 27 | return annotationName; 28 | } 29 | } -------------------------------------------------------------------------------- /modules/sbt-plugin/src/main/java/ch/epfl/scala/debugadapter/sbtplugin/SubclassFingerscan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * sbt 3 | * Copyright 2011 - 2018, Lightbend, Inc. 4 | * Copyright 2008 - 2010, Mark Harrah 5 | * Licensed under Apache License 2.0 (see LICENSE) 6 | */ 7 | 8 | package ch.epfl.scala.debugadapter.sbtplugin; 9 | 10 | import sbt.testing.SubclassFingerprint; 11 | 12 | import java.io.Serializable; 13 | 14 | public final class SubclassFingerscan implements SubclassFingerprint, Serializable { 15 | private final boolean isModule; 16 | private final String superclassName; 17 | private final boolean requireNoArgConstructor; 18 | 19 | public SubclassFingerscan(final SubclassFingerprint print) { 20 | isModule = print.isModule(); 21 | superclassName = print.superclassName(); 22 | requireNoArgConstructor = print.requireNoArgConstructor(); 23 | } 24 | 25 | public boolean isModule() { 26 | return isModule; 27 | } 28 | 29 | public String superclassName() { 30 | return superclassName; 31 | } 32 | 33 | public boolean requireNoArgConstructor() { 34 | return requireNoArgConstructor; 35 | } 36 | } -------------------------------------------------------------------------------- /modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/internal/DebugServerThreadPool.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.sbtplugin.internal 2 | 3 | import java.util.concurrent.atomic.AtomicInteger 4 | import java.util.concurrent.ThreadPoolExecutor 5 | 6 | private[debugadapter] object DebugServerThreadPool { 7 | private val nextThreadId = new AtomicInteger(1) 8 | private val threadGroup = Thread.currentThread.getThreadGroup 9 | 10 | private val threadFactory = new java.util.concurrent.ThreadFactory() { 11 | override def newThread(runnable: Runnable): Thread = { 12 | val thread = new Thread( 13 | threadGroup, 14 | runnable, 15 | s"debug-adapter-thread-${nextThreadId.getAndIncrement}" 16 | ) 17 | thread.setDaemon(true) 18 | thread 19 | } 20 | } 21 | 22 | val executor = new ThreadPoolExecutor( 23 | 1, // corePoolSize 24 | 4, // maximumPoolSize 25 | 2, 26 | java.util.concurrent.TimeUnit.SECONDS, // keep alive 2 seconds 27 | new java.util.concurrent.SynchronousQueue[Runnable](), 28 | threadFactory 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/internal/DebuggeeProcess.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.sbtplugin.internal 2 | 3 | import ch.epfl.scala.debugadapter.CancelableFuture 4 | import ch.epfl.scala.debugadapter.DebuggeeListener 5 | import sbt.ForkOptions 6 | import sbt.io.syntax._ 7 | 8 | import java.io.File 9 | import java.nio.file.Path 10 | import scala.concurrent.ExecutionContext 11 | import scala.concurrent.Future 12 | import scala.concurrent.Promise 13 | import scala.sys.process.Process 14 | import scala.util.Failure 15 | import scala.util.Success 16 | 17 | private class DebuggeeProcess(process: Process) extends CancelableFuture[Unit] { 18 | private val exited = Promise[Unit]() 19 | 20 | DebuggeeProcess.fork { () => 21 | val exitCode = process.exitValue() 22 | if (exitCode != 0) 23 | exited.failure( 24 | new Exception(s"""Nonzero exit code returned: $exitCode""".stripMargin) 25 | ) 26 | else exited.success(()) 27 | } 28 | 29 | override val future: Future[Unit] = exited.future 30 | 31 | override def cancel(): Unit = process.destroy() 32 | } 33 | 34 | private object DebuggeeProcess { 35 | private final val debugInterface: String = 36 | "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=n" 37 | 38 | def start( 39 | forkOptions: ForkOptions, 40 | classpath: Seq[Path], 41 | mainClass: String, 42 | arguments: Seq[String], 43 | listener: DebuggeeListener, 44 | logger: LoggerAdapter 45 | )(implicit ec: ExecutionContext): DebuggeeProcess = { 46 | val javaHome = forkOptions.javaHome.getOrElse(new File(System.getProperty("java.home"))) 47 | val javaBin = (javaHome / "bin" / "java").getAbsolutePath 48 | 49 | val classpathOption = classpath.mkString(File.pathSeparator) 50 | val envVars = forkOptions.envVars + ("CLASSPATH" -> classpathOption) 51 | 52 | // remove agentlib option specified by the user 53 | val jvmOptions = forkOptions.runJVMOptions.filter(opt => !opt.contains("-agentlib")) 54 | val command = Seq(javaBin, debugInterface) ++ jvmOptions ++ Some(mainClass) ++ arguments 55 | 56 | val builder = Process(command, forkOptions.workingDirectory, envVars.toSeq: _*) 57 | val processLogger = new DebuggeeProcessLogger(listener) 58 | 59 | logger.info("Starting debuggee process:") 60 | logger.info("- working directory:" + forkOptions.workingDirectory.getOrElse("null")) 61 | logger.info("- command: " + command.mkString(" ")) 62 | val formattedEnvVars = envVars.map { case (key, value) => s" $key=$value" }.mkString("\n") 63 | logger.info("- environment variables:\n" + formattedEnvVars) 64 | 65 | val process = new DebuggeeProcess(builder.run(processLogger)) 66 | process.future.onComplete { 67 | case Failure(cause) => 68 | logger.warn(s"Debuggee process failed with ${cause.getMessage}") 69 | case Success(()) => 70 | logger.info(s"Debuggee process terminated successfully.") 71 | } 72 | process 73 | } 74 | 75 | private def fork(f: () => Unit): Unit = { 76 | val thread = new Thread { 77 | override def run(): Unit = f() 78 | } 79 | thread.start() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/internal/DebuggeeProcessLogger.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.sbtplugin.internal 2 | 3 | import ch.epfl.scala.debugadapter.DebuggeeListener 4 | 5 | import java.net.InetSocketAddress 6 | import scala.sys.process.ProcessLogger 7 | 8 | private class DebuggeeProcessLogger(listener: DebuggeeListener) extends ProcessLogger { 9 | private final val JDINotificationPrefix = 10 | "Listening for transport dt_socket at address: " 11 | 12 | override def out(line: => String): Unit = { 13 | if (line.startsWith(JDINotificationPrefix)) { 14 | val port = Integer.parseInt(line.drop(JDINotificationPrefix.length)) 15 | val address = new InetSocketAddress("127.0.0.1", port) 16 | listener.onListening(address) 17 | } else { 18 | listener.out(line) 19 | } 20 | } 21 | override def err(line: => String): Unit = { 22 | listener.err(line) 23 | } 24 | override def buffer[T](f: => T): T = f 25 | } 26 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/internal/Error.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.sbtplugin.internal 2 | 3 | import sbt.internal.langserver.ErrorCodes 4 | 5 | private[debugadapter] final case class Error(code: Long, message: String) 6 | 7 | private[debugadapter] object Error { 8 | def paramsMissing(method: String): Error = 9 | Error(ErrorCodes.InvalidParams, s"param is expected on '$method' method.") 10 | def invalidParams(msg: String): Error = Error(ErrorCodes.InvalidParams, msg) 11 | def parseError(cause: Throwable): Error = 12 | Error(ErrorCodes.ParseError, cause.getMessage) 13 | } 14 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/internal/LoggerAdapter.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.sbtplugin.internal 2 | 3 | import ch.epfl.scala.debugadapter.Logger 4 | 5 | private[debugadapter] class LoggerAdapter(underlying: sbt.Logger) extends Logger { 6 | override def debug(msg: => String): Unit = () 7 | override def info(msg: => String): Unit = underlying.info(msg) 8 | override def warn(msg: => String): Unit = underlying.warn(msg) 9 | override def error(msg: => String): Unit = underlying.error(msg) 10 | override def trace(t: => Throwable): Unit = underlying.trace(t) 11 | } 12 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/internal/SbtDebugToolsResolver.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.sbtplugin.internal 2 | 3 | import ch.epfl.scala.debugadapter.DebugToolsResolver 4 | import ch.epfl.scala.debugadapter.ScalaVersion 5 | import sbt.librarymanagement.DependencyResolution 6 | import sbt.librarymanagement.UpdateConfiguration 7 | import sbt.librarymanagement.UnresolvedWarningConfiguration 8 | import sbt.librarymanagement.ModuleID 9 | import sbt.librarymanagement.UpdateReport 10 | import sbt.io.Hash 11 | import scala.util.Success 12 | import scala.util.Try 13 | import scala.util.Failure 14 | import ch.epfl.scala.debugadapter.BuildInfo 15 | import sbt.{ScalaVersion => _, _} 16 | import java.net.URLClassLoader 17 | import xsbti.compile.ScalaInstance 18 | 19 | class SbtDebugToolsResolver( 20 | scalaInstance: ScalaInstance, 21 | dependencyRes: DependencyResolution, 22 | updateConfig: UpdateConfiguration, 23 | warningConfig: UnresolvedWarningConfiguration, 24 | logger: xsbti.Logger 25 | ) extends DebugToolsResolver { 26 | 27 | override def resolveExpressionCompiler(scalaVersion: ScalaVersion): Try[ClassLoader] = { 28 | val org = BuildInfo.organization 29 | val artifact = s"${BuildInfo.expressionCompilerName}_$scalaVersion" 30 | val version = BuildInfo.version 31 | 32 | for (report <- fetchArtifactsOf(org % artifact % version, Seq.empty)) 33 | yield 34 | if (scalaInstance.version == scalaVersion.value) { 35 | val expressionCompilerJars = report 36 | .select( 37 | configurationFilter(Runtime.name), 38 | moduleFilter(org, artifact, version) | moduleFilter( 39 | "org.scala-lang.modules", 40 | "scala-collection-compat_2.12" 41 | ), 42 | artifactFilter(extension = "jar", classifier = "") 43 | ) 44 | .map(_.toURI.toURL) 45 | .toArray 46 | new URLClassLoader(expressionCompilerJars, scalaInstance.loader) 47 | } else { 48 | val expressionCompilerJars = report 49 | .select( 50 | configurationFilter(Runtime.name), 51 | moduleFilter(), 52 | artifactFilter(extension = "jar", classifier = "") 53 | ) 54 | .map(_.toURI.toURL) 55 | .toArray 56 | new URLClassLoader(expressionCompilerJars, null) 57 | } 58 | } 59 | 60 | override def resolveDecoder(scalaVersion: ScalaVersion): Try[Seq[java.nio.file.Path]] = { 61 | val org = BuildInfo.organization 62 | val artifact = s"${BuildInfo.decoderName}_3" 63 | val version = BuildInfo.version 64 | val tastyDep = "org.scala-lang" % "tasty-core_3" % scalaVersion.value 65 | 66 | for (report <- fetchArtifactsOf(org % artifact % version, Seq(tastyDep))) 67 | yield report 68 | .select(configurationFilter(Runtime.name), moduleFilter(), artifactFilter(extension = "jar", classifier = "")) 69 | .map(_.toPath) 70 | } 71 | 72 | private def fetchArtifactsOf(moduleID: ModuleID, dependencies: Seq[ModuleID]): Try[UpdateReport] = { 73 | val sha1 = Hash.toHex(Hash(moduleID.name)) 74 | val dummyID = ModuleID("ch.epfl.scala.temp", "temp-module" + sha1, moduleID.revision) 75 | .withConfigurations(moduleID.configurations) 76 | val descriptor = dependencyRes.moduleDescriptor(dummyID, moduleID +: dependencies.toVector, None) 77 | 78 | dependencyRes.update(descriptor, updateConfig, warningConfig, logger) match { 79 | case Right(report) => Success(report) 80 | case Left(warning) => Failure(warning.resolveException) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/internal/SbtTestSuiteEventHandler.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.sbtplugin.internal 2 | 3 | import ch.epfl.scala.debugadapter.DebuggeeListener 4 | import ch.epfl.scala.debugadapter.testing.TestSuiteEventHandler 5 | import ch.epfl.scala.debugadapter.testing.TestSuiteEvent 6 | 7 | /** 8 | * Extracts information about tests execution and send it to the DebuggeeListener. 9 | * Then DebugeeListener forwards it to the DAP client. 10 | */ 11 | class SbtTestSuiteEventHandler(listener: DebuggeeListener) extends TestSuiteEventHandler { 12 | 13 | def handle(event: TestSuiteEvent): Unit = 14 | event match { 15 | case TestSuiteEvent.Info(s) => listener.out(s) 16 | case TestSuiteEvent.Warn(s) => listener.out(s) 17 | case TestSuiteEvent.Error(s) => listener.err(s) 18 | case results: TestSuiteEvent.Results => 19 | val testResults = TestSuiteEventHandler.summarizeResults(results) 20 | listener.testResult(testResults) 21 | case _ => () 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/aggregate/build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / scalaVersion := "2.13.4" 2 | 3 | lazy val common = project.in(file("common")) 4 | 5 | lazy val root = project 6 | .in(file(".")) 7 | .aggregate(common) 8 | .settings( 9 | libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.2" 10 | ) 11 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/aggregate/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/aggregate/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/aggregate/src/main/scala/example/Hello.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | object Hello extends Greeting with App { 4 | println(greeting) 5 | } 6 | 7 | trait Greeting { 8 | lazy val greeting: String = "hello" 9 | } 10 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/aggregate/src/test/scala/example/HelloSpec.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import org.scalatest.flatspec.AnyFlatSpec 4 | import org.scalatest.matchers.should.Matchers 5 | 6 | class HelloSpec extends AnyFlatSpec with Matchers { 7 | "The Hello object" should "say hello" in { 8 | Hello.greeting shouldEqual "hello" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/aggregate/test: -------------------------------------------------------------------------------- 1 | > 'root / Test / startTestSuitesDebugSession ["example.HelloSpec"]' 2 | > 'root / Test / startMainClassDebugSession {"class":"example.Hello","arguments":[],"jvmOptions":[]}' 3 | > root / Test / stopDebugSession 4 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/attach/build.sbt: -------------------------------------------------------------------------------- 1 | import ch.epfl.scala.debugadapter.testfmk._ 2 | 3 | val checkDebugSession = inputKey[Unit]("Check the attach debug session") 4 | 5 | scalaVersion := "2.12.14" 6 | fork := true 7 | javaOptions += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=1045" 8 | checkDebugSession := { 9 | val uri = (Compile / startRemoteDebugSession).evaluated 10 | val source = (Compile / sources).value.head.toPath 11 | (Compile / bgRun).toTask("").value 12 | 13 | DebugTest.check(uri, attach = Some(1045))(Breakpoint(source, 5)) 14 | } 15 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/attach/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/attach/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/attach/src/main/scala/example/Main.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | object Main { 4 | def main(args: Array[String]): Unit = { 5 | println("Hello, World!") 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/attach/test: -------------------------------------------------------------------------------- 1 | > 'checkDebugSession {}' 2 | > stopDebugSession -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/debug-java/build.sbt: -------------------------------------------------------------------------------- 1 | import ch.epfl.scala.debugadapter.testfmk._ 2 | 3 | val checkDebugJava = inputKey[Unit]("Check that the -g option has been added") 4 | 5 | def checkDebugJavaTask = Def.inputTask { 6 | val uri = (Compile / startMainClassDebugSession).evaluated 7 | val source = (Compile / sources).value.head.toPath 8 | implicit val context: TestingContext = TestingContext(source, "3.3.0") 9 | DebugTest.check(uri)(Breakpoint(source, 6), Evaluation.success("x", 1)) 10 | } 11 | def checkDebugJavaWithGSpecializedTask = Def.inputTask { 12 | val uri = (Compile / startMainClassDebugSession).evaluated 13 | val source = (Compile / sources).value.head.toPath 14 | implicit val context: TestingContext = TestingContext(source, "3.3.0") 15 | DebugTest.check(uri)( 16 | Breakpoint(source, 6), 17 | // report runtime validation error if expression compiler cannot be resolved. 18 | Evaluation.failed("x", "x is not a local variable") 19 | ) 20 | } 21 | 22 | lazy val debugJavaWithG = 23 | project 24 | .in(file("./withG")) 25 | .settings( 26 | javacOptions += "-g", 27 | checkDebugJava := checkDebugJavaTask.evaluated 28 | ) 29 | 30 | lazy val debugJavaWithoutG = 31 | project 32 | .in(file("./withoutG")) 33 | .settings(checkDebugJava := checkDebugJavaTask.evaluated) 34 | 35 | lazy val debugJavaWithGSpecialized = 36 | project 37 | .in(file("./withGSpecialized")) 38 | .settings( 39 | javacOptions += "-g:lines,source", 40 | checkDebugJava := checkDebugJavaWithGSpecializedTask.evaluated 41 | ) 42 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/debug-java/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/debug-java/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/debug-java/test: -------------------------------------------------------------------------------- 1 | > 'debugJavaWithG / checkDebugJava {"class":"example.Main","arguments":[],"jvmOptions":[]}' 2 | > 'debugJavaWithoutG / checkDebugJava {"class":"example.Main","arguments":[],"jvmOptions":[]}' 3 | > 'debugJavaWithGSpecialized / checkDebugJava {"class":"example.Main","arguments":[],"jvmOptions":[]}' 4 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/debug-java/withG/src/main/java/example/Main.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | public class Main { 4 | public static void main(String[] args) { 5 | int x = 1; 6 | System.out.println("Hello, world!"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/debug-java/withGSpecialized/src/main/java/example/Main.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | public class Main { 4 | public static void main(String[] args) { 5 | int x = 1; 6 | System.out.println("Hello, world!"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/debug-java/withoutG/src/main/java/example/Main.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | public class Main { 4 | public static void main(String[] args) { 5 | int x = 1; 6 | System.out.println("Hello, world!"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/hot-code-reload/build.sbt: -------------------------------------------------------------------------------- 1 | import ch.epfl.scala.debugadapter.testfmk._ 2 | 3 | import java.nio.file.Path 4 | import scala.concurrent.ExecutionContext.Implicits.global 5 | 6 | val scalaV = "3.3.0" 7 | 8 | val checkBreakpoint = inputKey[Unit]("Check the breakpoint of a debug session") 9 | val checkHotCodeReplace = taskKey[Unit]("Check the hot code reloading of a debug session") 10 | val sourceToDebug = taskKey[Path]("The source file to be tested") 11 | 12 | def checkBreakpointTask = Def.inputTask { 13 | val uri = (Compile / startMainClassDebugSession).evaluated 14 | implicit val context: TestingContext = TestingContext(sourceToDebug.value, scalaV) 15 | DebugState.clientState = DebugTest.init(uri)(Outputed("A"), Breakpoint(6)) 16 | } 17 | 18 | def checkHotCodeReplaceTask = Def.task { 19 | val _ = (Compile / compile).value 20 | implicit val context: TestingContext = TestingContext(sourceToDebug.value, scalaV) 21 | DebugTest.runAndCheck(DebugState.client, DebugState.state, closeSession = true)( 22 | HotCodeReplace("A.m(): Unit"), 23 | Outputed("C"), 24 | Breakpoint(6), 25 | Outputed("D") 26 | ) 27 | } 28 | 29 | def checkNoHotCodeReplaceTask = Def.task { 30 | val _ = (Compile / compile).value 31 | DebugTest.runAndCheck(DebugState.client, DebugState.state, closeSession = true)(Outputed("B")) 32 | } 33 | 34 | lazy val a: Project = 35 | project 36 | .in(file(".")) 37 | .settings( 38 | scalaVersion := scalaV, 39 | checkBreakpoint := checkBreakpointTask.evaluated, 40 | checkHotCodeReplace := checkHotCodeReplaceTask.value, 41 | sourceToDebug := (Compile / sources).value.find(_.getName == "A.scala").get.toPath 42 | ) 43 | 44 | lazy val b = 45 | project 46 | .in(file("b")) 47 | .settings( 48 | scalaVersion := scalaV, 49 | checkBreakpoint := checkBreakpointTask.evaluated, 50 | checkHotCodeReplace := checkHotCodeReplaceTask.value, 51 | sourceToDebug := (a / sourceToDebug).value 52 | ) 53 | .dependsOn(a) 54 | 55 | lazy val c = 56 | project 57 | .in(file("c")) 58 | .settings( 59 | scalaVersion := scalaV, 60 | checkBreakpoint := checkBreakpointTask.evaluated, 61 | checkHotCodeReplace := checkNoHotCodeReplaceTask.value, 62 | sourceToDebug := (a / sourceToDebug).value 63 | ) 64 | .dependsOn(a) 65 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/hot-code-reload/project/DebugState.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.testfmk 2 | 3 | object DebugState { 4 | var clientState: (TestingDebugClient, DebugCheckState) = null 5 | 6 | def state: DebugCheckState = clientState._2 7 | def client: TestingDebugClient = clientState._1 8 | } 9 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/hot-code-reload/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 14 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/hot-code-reload/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/hot-code-reload/src/main/scala/example/A.scala.1: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | class A { 4 | def m(): Unit = { 5 | println("A") 6 | println("B") 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/hot-code-reload/src/main/scala/example/A.scala.2: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | class A { 4 | def m(): Unit = { 5 | println("C") 6 | println("D") 7 | } 8 | } -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/hot-code-reload/src/main/scala/example/Main.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | object Main { 4 | def main(args: Array[String]): Unit = 5 | new A().m() 6 | } 7 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/hot-code-reload/test: -------------------------------------------------------------------------------- 1 | $ copy-file ./src/main/scala/example/A.scala.1 ./src/main/scala/example/A.scala 2 | > 'a / checkBreakpoint {"class":"example.Main","arguments":[],"jvmOptions":[]}' 3 | $ copy-file ./src/main/scala/example/A.scala.2 ./src/main/scala/example/A.scala 4 | > a / checkHotCodeReplace 5 | > a / stopDebugSession 6 | 7 | $ copy-file ./src/main/scala/example/A.scala.1 ./src/main/scala/example/A.scala 8 | > 'b / checkBreakpoint {"class":"example.Main","arguments":[],"jvmOptions":[]}' 9 | $ copy-file ./src/main/scala/example/A.scala.2 ./src/main/scala/example/A.scala 10 | > b / checkHotCodeReplace 11 | > b / stopDebugSession 12 | 13 | $ copy-file ./src/main/scala/example/A.scala.1 ./src/main/scala/example/A.scala 14 | > 'c / checkBreakpoint {"class":"example.Main","arguments":[],"jvmOptions":[]}' 15 | > c / checkHotCodeReplace 16 | > c / stopDebugSession 17 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/integration-test-suite/build.sbt: -------------------------------------------------------------------------------- 1 | import ch.epfl.scala.debugadapter.sbtplugin.DebugAdapterPlugin 2 | import ch.epfl.scala.debugadapter.testfmk._ 3 | 4 | val checkDebugSession = 5 | inputKey[Unit]("Check the integration test suite debug session") 6 | 7 | val root = project 8 | .in(file(".")) 9 | .configs(IntegrationTest) 10 | .settings( 11 | scalaVersion := "2.12.14", 12 | libraryDependencies += "com.lihaoyi" %% "utest" % "0.7.10" % IntegrationTest, 13 | testFrameworks += new TestFramework("utest.runner.Framework"), 14 | Defaults.itSettings, 15 | inConfig(IntegrationTest)(DebugAdapterPlugin.testSettings), 16 | checkDebugSession := checkDebugSessionTask.evaluated 17 | ) 18 | 19 | def checkDebugSessionTask = Def.inputTask { 20 | val uri = (IntegrationTest / startTestSuitesDebugSession).evaluated 21 | val mainSource = (Compile / sources).value.head.toPath 22 | val suiteSource = (IntegrationTest / sources).value.head.toPath 23 | 24 | DebugTest.check(uri)( 25 | Breakpoint(suiteSource, 8), 26 | Breakpoint(mainSource, 5), 27 | Breakpoint(suiteSource, 10) 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/integration-test-suite/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/integration-test-suite/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/integration-test-suite/src/it/scala/example/MySuite.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import utest._ 4 | 5 | object MySuite extends TestSuite { 6 | def tests: Tests = Tests { 7 | "should allow breakpoints in both test and main" - { 8 | println("Running test") 9 | new Hello().greet() 10 | println("Test successful") 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/integration-test-suite/src/main/scala/example/Hello.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | class Hello { 4 | def greet(): Unit = { 5 | println("Hello, World!") 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/integration-test-suite/test: -------------------------------------------------------------------------------- 1 | > 'checkDebugSession ["example.MySuite"]' 2 | > stopDebugSession -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/main-class-scala3/build.sbt: -------------------------------------------------------------------------------- 1 | import scala.concurrent.ExecutionContext 2 | import ch.epfl.scala.debugadapter.testfmk._ 3 | 4 | val checkDebugSession = inputKey[Unit]("Check the main class debug session") 5 | 6 | scalaVersion := "3.0.0" 7 | checkDebugSession := { 8 | val uri = (Compile / startMainClassDebugSession).evaluated 9 | val source = (Compile / sources).value.head.toPath 10 | 11 | DebugTest.check(uri)(Breakpoint(source, 5)) 12 | } 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/main-class-scala3/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/main-class-scala3/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/main-class-scala3/src/main/scala/example/Main.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | object Main { 4 | def main(args: Array[String]): Unit = { 5 | println("Hello, World!") 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/main-class-scala3/test: -------------------------------------------------------------------------------- 1 | > 'checkDebugSession {"class":"example.Main","arguments":[],"jvmOptions":[]}' 2 | > stopDebugSession 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/main-class/build.sbt: -------------------------------------------------------------------------------- 1 | import ch.epfl.scala.debugadapter.testfmk._ 2 | 3 | val checkDebugSession = inputKey[Unit]("Check the main class debug session") 4 | 5 | scalaVersion := "2.12.14" 6 | checkDebugSession := { 7 | val uri = (Compile / startMainClassDebugSession).evaluated 8 | val source = (Compile / sources).value.head.toPath 9 | DebugTest.check(uri)(Breakpoint(source, 5)) 10 | } 11 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/main-class/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/main-class/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/main-class/src/main/scala/example/Main.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | object Main { 4 | def main(args: Array[String]): Unit = { 5 | println("Hello, World!") 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/main-class/test: -------------------------------------------------------------------------------- 1 | > 'checkDebugSession {"class":"example.Main","arguments":[],"jvmOptions":[]}' 2 | > stopDebugSession 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/native-lib/build.sbt: -------------------------------------------------------------------------------- 1 | import ch.epfl.scala.debugadapter.testfmk._ 2 | 3 | val checkDebugSession = inputKey[Unit]("Check the test suite debug session") 4 | 5 | libraryDependencies += "org.lmdbjava" % "lmdbjava" % "0.8.2" 6 | scalaVersion := "2.13.7" 7 | 8 | checkDebugSession := { 9 | val uri = (Compile / startMainClassDebugSession).evaluated 10 | DebugTest.check(uri)(Outputed("Success")) 11 | } 12 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/native-lib/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/native-lib/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/native-lib/src/main/scala/example/Main.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import java.nio.file.Files 4 | import scala.util.control.NonFatal 5 | import scala.util.Properties 6 | 7 | object Main extends App { 8 | if (Properties.javaVersion.startsWith("17")) { 9 | // There is no support for lmdbjava in Java 17 10 | () 11 | } else { 12 | val env = org.lmdbjava.Env.create() 13 | val folder = Files.createTempDirectory("test").toFile 14 | env.open(folder) 15 | } 16 | println("Success") 17 | } 18 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/native-lib/test: -------------------------------------------------------------------------------- 1 | > 'checkDebugSession {"class":"example.Main","arguments":[],"jvmOptions":[]}' 2 | > stopDebugSession 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/no-expression-compiler/build.sbt: -------------------------------------------------------------------------------- 1 | import ch.epfl.scala.debugadapter.testfmk._ 2 | 3 | val checkDebugSession = inputKey[Unit]("Check the main class debug session") 4 | 5 | // there is no expression compiler for Scala 2.11 6 | scalaVersion := "2.11.12" 7 | 8 | // check that one can start a debug session even if the expression compiler 9 | // cannot be resolved 10 | checkDebugSession := { 11 | val uri = (Compile / startMainClassDebugSession).evaluated 12 | val source = (Compile / sources).value.head.toPath 13 | 14 | implicit val ctx = TestingContext(source, scalaVersion.value) 15 | DebugTest.check(uri)(Breakpoint(4), Evaluation.failed("s\"${1 + 1}\"", "Cannot evaluate")) 16 | } 17 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/no-expression-compiler/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/no-expression-compiler/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/no-expression-compiler/src/main/scala/example/Main.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | object Main extends App { 4 | println("Hello") 5 | } 6 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/no-expression-compiler/test: -------------------------------------------------------------------------------- 1 | > 'checkDebugSession {"class":"example.Main","arguments":[],"jvmOptions":[]}' 2 | > stopDebugSession 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/scala-collection-compat/build.sbt: -------------------------------------------------------------------------------- 1 | import ch.epfl.scala.debugadapter.testfmk._ 2 | import ch.epfl.scala.debugadapter.DebugConfig 3 | 4 | val scalaCollectionCompat = inputKey[Unit]("Check the presence of scala-collection-compat") 5 | val scalaV = "2.12.20" 6 | 7 | def checkScalaCollectionCompat = Def.inputTask { 8 | val uri = (Compile / startMainClassDebugSession).evaluated 9 | val source = (Compile / sources).value.head.toPath 10 | implicit val context: TestingContext = TestingContext(source, scalaV) 11 | DebugTest.check(uri)(Breakpoint(source, 6), Evaluation.success("x", 1)) 12 | } 13 | 14 | lazy val scala212Compat = 15 | project 16 | .in(file(".")) 17 | .settings( 18 | scalaVersion := scalaV, 19 | scalaCollectionCompat := checkScalaCollectionCompat.evaluated, 20 | debugAdapterConfig := DebugConfig.default.copy(evaluationMode = DebugConfig.ScalaEvaluationOnly) 21 | ) 22 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/scala-collection-compat/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.9.0 -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/scala-collection-compat/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/scala-collection-compat/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/scala-collection-compat/src/main/scala/example/Main.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | object Main { 4 | def main(args: Array[String]) = { 5 | val x = 1; 6 | println("Hello, world!"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/scala-collection-compat/test: -------------------------------------------------------------------------------- 1 | > 'scala212Compat / scalaCollectionCompat {"class":"example.Main","arguments":[],"jvmOptions":[]}' 2 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/step-into-jdk/build.sbt: -------------------------------------------------------------------------------- 1 | import scala.util.Properties 2 | import ch.epfl.scala.debugadapter.testfmk._ 3 | import java.nio.file.Paths 4 | 5 | val checkDebugSession = inputKey[Unit]("Check the main class debug session") 6 | 7 | val jdkHome = Paths.get(Properties.jdkHome) 8 | val jdkSourceZip = jdkHome.resolve("src.zip") 9 | 10 | scalaVersion := "2.12.14" 11 | javaHome := Some(jdkHome.toFile) 12 | checkDebugSession := { 13 | val uri = (Compile / startMainClassDebugSession).evaluated 14 | val source = (Compile / sources).value.head.toPath 15 | 16 | DebugTest.check(uri)( 17 | Breakpoint(source, 5), 18 | StepIn.method("PrintStream.println(String): void") 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/step-into-jdk/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/step-into-jdk/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/step-into-jdk/src/main/scala/example/Main.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | object Main { 4 | def main(args: Array[String]): Unit = { 5 | System.out.println("foo") 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/step-into-jdk/test: -------------------------------------------------------------------------------- 1 | > 'checkDebugSession {"class":"example.Main","arguments":[],"jvmOptions":[]}' 2 | > stopDebugSession 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/stepfilter-config/build.sbt: -------------------------------------------------------------------------------- 1 | import ch.epfl.scala.debugadapter.testfmk._ 2 | import ch.epfl.scala.debugadapter.StepFiltersConfig 3 | 4 | val checkStepFilterOverriding = 5 | inputKey[Unit]("Check that step filter configuration is overridden with LaunchArguments") 6 | 7 | scalaVersion := "3.3.2" 8 | checkStepFilterOverriding := { 9 | val uri = (Compile / startMainClassDebugSession).evaluated 10 | val source = (Compile / sources).value.head.toPath 11 | DebugTest.check( 12 | uri, 13 | stepFilters = 14 | StepFiltersConfig(skipForwardersAndAccessors = true, skipRuntimeClasses = true, skipClassLoading = false) 15 | )( 16 | Breakpoint(source, 7), 17 | StepIn.method("ClassLoader.loadClass(String): Class") 18 | ) 19 | } 20 | 21 | val checkNoStepFilterOverridingWhenNoLaunchArgs = 22 | inputKey[Unit]("Check that step filter configuration is the default one when no LaunchArguments are provided") 23 | 24 | scalaVersion := "3.3.2" 25 | checkNoStepFilterOverridingWhenNoLaunchArgs := { 26 | val uri = (Compile / startMainClassDebugSession).evaluated 27 | val source = (Compile / sources).value.head.toPath 28 | DebugTest.check(uri)(Breakpoint(source, 7), StepIn.method("Foo.(x: Int): Unit")) 29 | } 30 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/stepfilter-config/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/stepfilter-config/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/stepfilter-config/src/main/scala/example/Main.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | case class Foo(x: Int) 4 | 5 | object Main { 6 | def main(args: Array[String]): Unit = 7 | Foo(42) 8 | } 9 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/stepfilter-config/test: -------------------------------------------------------------------------------- 1 | > 'checkStepFilterOverriding {"class":"example.Main","arguments":[],"jvmOptions":[]}' 2 | > 'checkNoStepFilterOverridingWhenNoLaunchArgs {"class":"example.Main","arguments":[],"jvmOptions":[]}' 3 | > stopDebugSession 4 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-main-class/build.sbt: -------------------------------------------------------------------------------- 1 | import ch.epfl.scala.debugadapter.testfmk._ 2 | 3 | val checkDebugSession = inputKey[Unit]("Check the main class debug session") 4 | 5 | scalaVersion := "2.12.14" 6 | checkDebugSession := { 7 | val uri = (Test / startMainClassDebugSession).evaluated 8 | val source = (Test / sources).value.head.toPath 9 | DebugTest.check(uri)(Breakpoint(source, 5)) 10 | } 11 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-main-class/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-main-class/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-main-class/src/test/scala/example/Main.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | object Main { 4 | def main(args: Array[String]): Unit = { 5 | println("Hello, World!") 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-main-class/test: -------------------------------------------------------------------------------- 1 | > 'checkDebugSession {"class":"example.Main","arguments":[],"jvmOptions":[]}' 2 | > stopDebugSession 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-selection-suite/build.sbt: -------------------------------------------------------------------------------- 1 | import ch.epfl.scala.debugadapter.testfmk._ 2 | import munit.Assertions._ 3 | 4 | libraryDependencies += "com.github.sbt" % "junit-interface" % "0.13.3" % Test 5 | testFrameworks += TestFrameworks.JUnit 6 | scalaVersion := "2.12.14" 7 | 8 | Test / fork := true 9 | 10 | InputKey[Unit]("checkDebugSession") := { 11 | val uri = (Test / startTestSuitesSelectionDebugSession).evaluated 12 | 13 | // Xmx is set to 1G 14 | // 1GB = 1024 * 1024 * 1024 = 1073741824 15 | // env variable KEY is set to the VALUE 16 | 17 | // for some reason jdk8 yields 954728448 for Xmx1G 18 | val isJava8 = System.getProperty("java.version").startsWith("1.8.") 19 | val expected = if (isJava8) "test1_954728448_VALUE" else "test1_1073741824_VALUE" 20 | 21 | DebugTest.check(uri)( 22 | Outputed(msg => assert(msg.contains("test2"))), 23 | Outputed(expected) 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-selection-suite/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-selection-suite/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-selection-suite/src/test/scala/example/TestSuite.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import org.junit.Test 4 | 5 | class TestSuite { 6 | 7 | @Test 8 | def test1(): Unit = { 9 | val mem = Runtime.getRuntime().maxMemory() 10 | val env = System.getenv("KEY") 11 | val out = s"test1_${mem}_$env" 12 | println(out) 13 | } 14 | 15 | @Test 16 | def test2(): Unit = { 17 | println("test2") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-selection-suite/test: -------------------------------------------------------------------------------- 1 | > 'checkDebugSession {"suites":[{"className":"example.TestSuite","tests":["test1"]}],"jvmOptions":["-Xmx1G"],"environmentVariables":["KEY=VALUE"]}' 2 | > stopDebugSession 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-suite/build.sbt: -------------------------------------------------------------------------------- 1 | import ch.epfl.scala.debugadapter.testfmk._ 2 | 3 | val checkDebugSession = inputKey[Unit]("Check the test suite debug session") 4 | 5 | libraryDependencies += "com.lihaoyi" %% "utest" % "0.7.10" % Test 6 | testFrameworks += new TestFramework("utest.runner.Framework") 7 | scalaVersion := "2.12.14" 8 | checkDebugSession := { 9 | val uri = (Test / startTestSuitesDebugSession).evaluated 10 | val mainSource = (Compile / sources).value.head.toPath 11 | val suiteSource = (Test / sources).value.head.toPath 12 | 13 | DebugTest.check(uri)(Breakpoint(suiteSource, 8), Breakpoint(mainSource, 5), Breakpoint(suiteSource, 10)) 14 | } 15 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-suite/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-suite/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-suite/src/main/scala/example/Hello.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | class Hello() { 4 | def greet(): Unit = { 5 | println("Breakpoint in hello class") 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-suite/src/test/scala/example/MySuite.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import utest._ 4 | 5 | object MySuite extends TestSuite { 6 | def tests: Tests = Tests { 7 | "should allow breakpoints in both test and main" - { 8 | println("Breakpoint in test") 9 | new Hello().greet() 10 | println("Finished all breakpoints") 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/test-suite/test: -------------------------------------------------------------------------------- 1 | > 'checkDebugSession ["example.MySuite"]' 2 | > stopDebugSession 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/unmanaged-jars/build.sbt: -------------------------------------------------------------------------------- 1 | import scala.util.Properties 2 | import ch.epfl.scala.debugadapter.testfmk._ 3 | import java.nio.file.Paths 4 | 5 | val checkDebugSession = inputKey[Unit]("Check the main class debug session") 6 | 7 | val jdkHome = Paths.get(Properties.jdkHome) 8 | 9 | scalaVersion := "2.12.14" 10 | javaHome := Some(jdkHome.toFile) 11 | checkDebugSession := { 12 | val uri = (Compile / startMainClassDebugSession).evaluated 13 | DebugTest.check(uri)(Outputed("com.sun.jdi.Value")) 14 | } 15 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/unmanaged-jars/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val pluginVersion = sys.props 2 | .get("plugin.version") 3 | .getOrElse { 4 | sys.error( 5 | """|The system property 'plugin.version' is not defined. 6 | |Specify this property using the scriptedLaunchOpts -D. 7 | |""".stripMargin 8 | ) 9 | } 10 | 11 | addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % pluginVersion) 12 | libraryDependencies += "ch.epfl.scala" %% "scala-debug-adapter-test" % pluginVersion 13 | 14 | // this plugin add the tools.jar from the JDK into the classpath as an unmanaged jar. 15 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 16 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/unmanaged-jars/project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/unmanaged-jars/src/main/scala/example/Main.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | // this import comes from an unmnaged jar 4 | import com.sun.jdi.Value 5 | 6 | object Main { 7 | def main(args: Array[String]): Unit = { 8 | val someJdiClass = classOf[Value] 9 | System.out.println(someJdiClass.getName) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /modules/sbt-plugin/src/sbt-test/debug-session/unmanaged-jars/test: -------------------------------------------------------------------------------- 1 | > 'checkDebugSession {"class":"example.Main","arguments":[],"jvmOptions":[]}' 2 | > stopDebugSession 3 | -------------------------------------------------------------------------------- /modules/tests/input/class-entry/empty-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalacenter/scala-debug-adapter/72fcfb4eb0021a0622ca9c4a70a3165ef4bf2fc2/modules/tests/input/class-entry/empty-sources.jar -------------------------------------------------------------------------------- /modules/tests/input/class-entry/empty.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalacenter/scala-debug-adapter/72fcfb4eb0021a0622ca9c4a70a3165ef4bf2fc2/modules/tests/input/class-entry/empty.jar -------------------------------------------------------------------------------- /modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/CommonFunSuite.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.testfmk 2 | 3 | import scala.concurrent.duration.* 4 | import scala.util.Properties 5 | 6 | trait CommonFunSuite extends munit.FunSuite with CommonUtils { 7 | override def munitTimeout: Duration = 8 | defaultTimeout(super.munitTimeout) 9 | } 10 | 11 | trait CommonUtils { 12 | def isJava8: Boolean = Properties.javaVersion.startsWith("1.8") 13 | 14 | def isDebug: Boolean = DebugUtils.isDebug 15 | 16 | def defaultTimeout(timeout: Duration) = if (isDebug) 8.hours else timeout 17 | } 18 | -------------------------------------------------------------------------------- /modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/DebugUtils.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.testfmk 2 | 3 | import java.lang.management.ManagementFactory 4 | import scala.jdk.CollectionConverters.* 5 | 6 | object DebugUtils { 7 | def isDebug: Boolean = { 8 | val mxBean = ManagementFactory.getRuntimeMXBean 9 | mxBean.getInputArguments.asScala.exists(_.contains("jdwp")) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/GithubUtils.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter 2 | 3 | object GithubUtils { 4 | def isCI(): Boolean = System.getenv("CI") != null 5 | } 6 | -------------------------------------------------------------------------------- /modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/MockDebuggee.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.testfmk 2 | 3 | import scala.concurrent.duration._ 4 | import scala.concurrent.{Await, Future, Promise} 5 | import scala.language.postfixOps 6 | import ch.epfl.scala.debugadapter.Debuggee 7 | import ch.epfl.scala.debugadapter.ScalaVersion 8 | import ch.epfl.scala.debugadapter.DebuggeeListener 9 | import ch.epfl.scala.debugadapter.CancelableFuture 10 | import ch.epfl.scala.debugadapter.Module 11 | import ch.epfl.scala.debugadapter.Library 12 | import ch.epfl.scala.debugadapter.UnmanagedEntry 13 | import ch.epfl.scala.debugadapter.JavaRuntime 14 | import java.io.Closeable 15 | 16 | class MockDebuggee extends Debuggee { 17 | val scalaVersion: ScalaVersion = ScalaVersion.`2.12` 18 | var currentProcess: MockCancelableFuture = _ 19 | 20 | override def name: String = "mock" 21 | 22 | override def run(listener: DebuggeeListener): CancelableFuture[Unit] = { 23 | if (currentProcess != null) { 24 | // wait for the current process to finish 25 | Await.result(currentProcess.future, 500 millis) 26 | } 27 | currentProcess = new MockCancelableFuture() 28 | currentProcess 29 | } 30 | 31 | override def modules: Seq[Module] = Seq.empty 32 | override def libraries: Seq[Library] = Seq.empty 33 | override def unmanagedEntries: Seq[UnmanagedEntry] = Seq.empty 34 | 35 | override def javaRuntime: Option[JavaRuntime] = None 36 | 37 | override def observeClassUpdates(onClassUpdate: Seq[String] => Unit): Closeable = () => () 38 | } 39 | 40 | class MockCancelableFuture() extends CancelableFuture[Unit] { 41 | val stopped: Promise[Unit] = Promise[Unit]() 42 | override def future: Future[Unit] = stopped.future 43 | override def cancel(): Unit = stopped.trySuccess(()) 44 | } 45 | -------------------------------------------------------------------------------- /modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/ScalaInstance.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.testfmk 2 | 3 | import ch.epfl.scala.debugadapter.Library 4 | import java.net.URLClassLoader 5 | import java.nio.file.Path 6 | import ch.epfl.scala.debugadapter.ClassPathEntry 7 | import java.io.File 8 | 9 | sealed abstract class ScalaInstance( 10 | val libraryJars: Seq[Library], 11 | compilerJars: Seq[Library], 12 | expressionCompilerJar: Library, 13 | val decoderJars: Seq[Library] 14 | ) { 15 | val libraryClassLoader = new URLClassLoader(libraryJars.map(_.toURL).toArray, null) 16 | val compilerClassLoader = new URLClassLoader(compilerJars.map(_.toURL).toArray, libraryClassLoader) 17 | val expressionCompilerClassLoader = new URLClassLoader(Array(expressionCompilerJar.toURL), compilerClassLoader) 18 | 19 | def compile( 20 | classDir: Path, 21 | classPath: Seq[ClassPathEntry], 22 | scalacOptions: Seq[String], 23 | sourceFiles: Seq[Path] 24 | ): Unit = { 25 | val args = Array( 26 | "-d", 27 | classDir.toString, 28 | "-classpath", 29 | classPath.map(_.absolutePath).mkString(File.pathSeparator) 30 | ) ++ 31 | scalacOptions ++ 32 | sourceFiles.map(_.toString) 33 | compileInternal(args) 34 | } 35 | 36 | protected def compileInternal(args: Array[String]): Unit 37 | } 38 | 39 | final class Scala2Instance( 40 | libraryJars: Seq[Library], 41 | compilerJars: Seq[Library], 42 | expressionCompilerJar: Library 43 | ) extends ScalaInstance(libraryJars, compilerJars, expressionCompilerJar, Seq.empty) { 44 | override protected def compileInternal(args: Array[String]): Unit = { 45 | val main = compilerClassLoader.loadClass("scala.tools.nsc.Main") 46 | val process = main.getMethod("process", classOf[Array[String]]) 47 | val success = process.invoke(null, args).asInstanceOf[Boolean] 48 | if (!success) throw new Exception("compilation failed") 49 | } 50 | } 51 | 52 | final class Scala3Instance( 53 | libraryJars: Seq[Library], 54 | compilerJars: Seq[Library], 55 | expressionCompilerJar: Library, 56 | stepFilterJars: Seq[Library] 57 | ) extends ScalaInstance(libraryJars, compilerJars, expressionCompilerJar, stepFilterJars) { 58 | override protected def compileInternal(args: Array[String]): Unit = { 59 | val main = compilerClassLoader.loadClass("dotty.tools.dotc.Main") 60 | val process = main.getMethod("process", classOf[Array[String]]) 61 | val classOfReporter = 62 | compilerClassLoader.loadClass("dotty.tools.dotc.reporting.Reporter") 63 | val hasErrors = classOfReporter.getMethod("hasErrors") 64 | val reporter = process.invoke(null, args) 65 | val success = !(hasErrors.invoke(reporter).asInstanceOf[Boolean]) 66 | if (!success) throw new Exception("compilation failed") 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingContext.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.testfmk 2 | 3 | import java.nio.file.Path 4 | import ch.epfl.scala.debugadapter.ScalaVersion 5 | 6 | trait TestingContext { 7 | def mainSource: Path 8 | def scalaVersion: ScalaVersion 9 | } 10 | 11 | object TestingContext { 12 | def apply(source: Path, sv: String): TestingContext = 13 | new TestingContext { 14 | val mainSource: Path = source 15 | val scalaVersion: ScalaVersion = ScalaVersion(sv) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingLoggers.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.testfmk 2 | 3 | import ch.epfl.scala.debugadapter.Logger 4 | 5 | object NoopLogger extends Logger { 6 | override def debug(msg: => String): Unit = () 7 | override def info(msg: => String): Unit = () 8 | override def warn(msg: => String): Unit = () 9 | override def error(msg: => String): Unit = () 10 | override def trace(t: => Throwable): Unit = () 11 | } 12 | 13 | /** 14 | * Can be used in DebugServer for debugging 15 | */ 16 | object PrintLogger extends Logger { 17 | override def debug(msg: => String): Unit = println(s"${Console.YELLOW}[DEBUG] $msg${Console.RESET}") 18 | override def info(msg: => String): Unit = println(s"${Console.CYAN}[INFO] $msg${Console.RESET}") 19 | override def warn(msg: => String): Unit = println(s"${Console.MAGENTA}[WARN] $msg${Console.RESET}") 20 | override def error(msg: => String): Unit = System.err.println(s"${Console.RED}[ERROR] $msg${Console.RESET}") 21 | override def trace(t: => Throwable): Unit = t.printStackTrace() 22 | } 23 | -------------------------------------------------------------------------------- /modules/tests/src/test/scala/ch/epfl/scala/debugadapter/LocalVariableTests.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import ch.epfl.scala.debugadapter.ScalaVersion 4 | import ch.epfl.scala.debugadapter.testfmk.* 5 | 6 | class LocalVariableTests extends DebugTestSuite { 7 | val scalaVersion = ScalaVersion.`3.1+` 8 | 9 | test("Should set the right expression for array elements") { 10 | val source = 11 | """|package example 12 | | 13 | |object Main { 14 | | def main(args: Array[String]): Unit = { 15 | | val array = Array(1, 2, 3) 16 | | println("ok") 17 | | } 18 | |}""".stripMargin 19 | implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) 20 | val regexp = """array\(\d+\)""".r 21 | check( 22 | Breakpoint(6), 23 | LocalVariable.inspect("array")( 24 | _.forall(v => """array\(\d+\)""".r.unapplySeq(v.evaluateName).isDefined) 25 | ) 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/MixedEvaluationTests.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import ch.epfl.scala.debugadapter.testfmk.* 4 | import ch.epfl.scala.debugadapter.ScalaVersion 5 | import ch.epfl.scala.debugadapter.DebugConfig 6 | 7 | object MixedEvaluationTestsSource { 8 | val source = 9 | """|package example 10 | | 11 | |object Main { 12 | | def main(args: Array[String]): Unit = { 13 | | implicit val x: Int = 42 14 | | val foo = Foo() 15 | | println(foo.wrongEval) 16 | | } 17 | |} 18 | | 19 | |trait SuperFoo { 20 | | def wrongEval: Boolean = true 21 | |} 22 | | 23 | |case class Foo() extends SuperFoo { 24 | | def wrongEval(implicit x: Int): Boolean = false 25 | |} 26 | |""".stripMargin 27 | } 28 | 29 | class Scala212MixedEvaluationTests extends MixedEvaluationTests(ScalaVersion.`2.12`) 30 | class Scala213MixedEvaluationTests extends MixedEvaluationTests(ScalaVersion.`2.13`) 31 | class Scala3MixedEvaluationTests extends MixedEvaluationTests(ScalaVersion.`3.1+`) 32 | 33 | abstract class MixedEvaluationTests(val scalaVersion: ScalaVersion) extends DebugTestSuite { 34 | lazy val localVar = 35 | TestingDebuggee.mainClass(MixedEvaluationTestsSource.source, "example.Main", scalaVersion) 36 | 37 | test("runtime evaluation resolve wrong overloads") { 38 | implicit val debuggee = localVar 39 | check(defaultConfig.copy(evaluationMode = DebugConfig.RuntimeEvaluationOnly))( 40 | Breakpoint(7), 41 | Evaluation.success("foo.wrongEval", true), 42 | Evaluation.success("foo.wrongEval(42)", false) 43 | ) 44 | check(defaultConfig.copy(evaluationMode = DebugConfig.MixedEvaluation))( 45 | Breakpoint(7), 46 | Evaluation.success("foo.wrongEval", false) 47 | ) 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/SourceLookUpProviderSpec.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import coursier._ 4 | import java.io.File 5 | import ch.epfl.scala.debugadapter.SourceJar 6 | import ch.epfl.scala.debugadapter.Library 7 | import ch.epfl.scala.debugadapter.testfmk.NoopLogger 8 | import munit.FunSuite 9 | import scala.util.Properties 10 | 11 | class SourceLookUpProviderSpec extends FunSuite { 12 | test("fix https://github.com/scalameta/metals/issues/3477#issuecomment-1013458270") { 13 | assume(!Properties.isMac) // TODO not working on MacOS 14 | val artifacts = coursier 15 | .Fetch() 16 | .addDependencies(Dependency(Module(Organization("org.openjfx"), ModuleName("javafx-controls")), "17.0.1")) 17 | .addClassifiers(Classifier.sources, Classifier("win"), Classifier("linux"), Classifier("mac")) 18 | .withMainArtifacts() 19 | .run() 20 | 21 | val classPath = artifacts 22 | .groupBy(getArtifactId) 23 | .values 24 | .flatMap { group => 25 | val sourcesJar = group.find(_.getName.endsWith("-sources.jar")).get 26 | val winJar = group.find(_.getName.endsWith("-win.jar")).get 27 | val linuxJar = group.find(_.getName.endsWith("-linux.jar")).get 28 | val macJar = group.find(_.getName.endsWith("-mac.jar")).get 29 | val mainJar = group 30 | .find(file => !Set(sourcesJar, winJar, linuxJar, macJar).contains(file)) 31 | .get 32 | val sourceEntries = Seq(SourceJar(sourcesJar.toPath)) 33 | Seq(winJar, linuxJar, macJar, mainJar) 34 | .map(_.toPath) 35 | .map(path => Library("javafx-controls", "17.0.1", path, sourceEntries)) 36 | } 37 | .toSeq 38 | 39 | for (_ <- 0 until 10) SourceLookUpProvider(classPath, NoopLogger) 40 | } 41 | 42 | private def getArtifactId(file: File): String = { 43 | file.getName 44 | .stripSuffix(".jar") 45 | .stripSuffix("-sources") 46 | .stripSuffix("-win") 47 | .stripSuffix("-linux") 48 | .stripSuffix("-mac") 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/SourceLookUpProviderStats.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.scala.debugadapter.internal 2 | 3 | import coursier._ 4 | import ch.epfl.scala.debugadapter.testfmk.TestingResolver 5 | import ch.epfl.scala.debugadapter.testfmk.NoopLogger 6 | import munit.FunSuite 7 | 8 | class SourceLookUpProviderStats extends FunSuite { 9 | test("scaladex") { 10 | printAndCheck("scaladex (Test)")( 11 | dep("ch.qos.logback:logback-classic:1.1.7"), 12 | dep("com.typesafe.scala-logging:scala-logging_2.13:3.9.2"), 13 | dep("com.getsentry.raven:raven-logback:8.0.3"), 14 | dep("org.scalatest:scalatest_2.13:3.0.9"), 15 | dep("com.github.nscala-time:nscala-time_2.13:2.24.0"), 16 | dep("com.typesafe.akka:akka-http-core_2.13:10.1.12"), 17 | dep("com.sksamuel.elastic4s:elastic4s-client-esjava_2.13:7.10.2"), 18 | dep("org.json4s:json4s-native_2.13:3.6.9"), 19 | dep("org.typelevel:jawn-json4s_2.13:1.0.0"), 20 | dep("com.typesafe.play:play-json_2.13:2.9.0"), 21 | dep("com.typesafe.akka:akka-testkit_2.13:2.6.5"), 22 | dep("com.typesafe.akka:akka-slf4j_2.13:2.6.5"), 23 | dep("ch.megard:akka-http-cors_2.13:0.4.3"), 24 | dep("com.softwaremill.akka-http-session:core_2.13:0.5.11"), 25 | dep("com.typesafe.akka:akka-http_2.13:10.1.12"), 26 | dep("org.webjars.bower:bootstrap-sass:3.3.6"), 27 | dep("org.webjars.bower:bootstrap-switch:3.3.2"), 28 | dep("org.webjars.bower:bootstrap-select:1.10.0"), 29 | dep("org.webjars.bower:font-awesome:4.6.3"), 30 | dep("org.webjars.bower:jquery:2.2.4"), 31 | dep("org.webjars.bower:raven-js:3.11.0"), 32 | dep("org.webjars.bower:select2:4.0.3"), 33 | dep("org.apache.logging.log4j:log4j-core:2.13.3"), 34 | dep("com.lihaoyi:fastparse_2.13:2.3.0"), 35 | dep("org.scala-lang.modules:scala-parallel-collections_2.13:0.2.0"), 36 | dep("com.typesafe.akka:akka-stream_2.13:2.6.5"), 37 | dep("me.tongfei:progressbar:0.5.5"), 38 | dep("org.apache.maven:maven-model-builder:3.3.9"), 39 | dep("org.jsoup:jsoup:1.10.1"), 40 | dep("com.typesafe.play:play-ahc-ws_2.13:2.8.2"), 41 | dep("org.apache.ivy:ivy:2.4.0"), 42 | dep("de.heikoseeberger:akka-http-json4s_2.13:1.29.1") 43 | )(35136, 4) 44 | } 45 | 46 | private def dep(coord: String): Dependency = { 47 | val parts = coord.split(':') 48 | Dependency(Module(Organization(parts(0)), ModuleName(parts(1))), parts(2)) 49 | } 50 | 51 | private def printAndCheck( 52 | project: String 53 | )(deps: Dependency*)(expectedClasses: Int, expectedOrphans: Int): Unit = { 54 | val classPath = TestingResolver.fetch(deps: _*) 55 | val (duration, lookUp) = 56 | TimeUtils.timed(SourceLookUpProvider(classPath, NoopLogger)) 57 | val entriesCount = lookUp.classPathEntries.size 58 | val classCount = lookUp.allClassNames.size 59 | val orphanClassCount = lookUp.allOrphanClasses.size 60 | println(s"$project:") 61 | println(s" - $entriesCount class path entries loaded in $duration") 62 | println(s" - $classCount classes") 63 | if (orphanClassCount != 0) { 64 | val orphanClassPercent = 65 | (orphanClassCount * 100000 / classCount).toFloat / 1000 66 | println(s" - $orphanClassCount orphan classes ($orphanClassPercent%)") 67 | } 68 | assert(classCount == expectedClasses, orphanClassCount == expectedOrphans) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /project/ContrabandConfig.scala: -------------------------------------------------------------------------------- 1 | import sbt.contraband.ast._ 2 | import sbt.contraband.CodecCodeGen 3 | 4 | object ContrabandConfig { 5 | 6 | /** Extract the only type parameter from a TpeRef */ 7 | def oneArg(tpe: Type): Type = { 8 | val pat = s"""${tpe.removeTypeParameters.name}[<\\[](.+?)[>\\]]""".r 9 | val pat(arg0) = tpe.name 10 | NamedType(arg0 split '.' toList) 11 | } 12 | 13 | /** Extract the two type parameters from a TpeRef */ 14 | def twoArgs(tpe: Type): List[Type] = { 15 | val pat = s"""${tpe.removeTypeParameters.name}[<\\[](.+?), (.+?)[>\\]]""".r 16 | val pat(arg0, arg1) = tpe.name 17 | NamedType(arg0 split '.' toList) :: NamedType(arg1 split '.' toList) :: Nil 18 | } 19 | 20 | /** sbt codecs */ 21 | val sbtCodecs: PartialFunction[String, Type => List[String]] = { 22 | // sbt-contraband should handle them by default 23 | case "Option" | "Set" | "scala.Vector" => tpe => getFormats(oneArg(tpe)) 24 | case "Map" | "Tuple2" | "scala.Tuple2" => 25 | tpe => twoArgs(tpe).flatMap(getFormats) 26 | case "Int" | "Long" => _ => Nil 27 | case "scalajson.ast.unsafe.JValue" | "sjsonnew.shaded.scalajson.ast.unsafe.JValue" => 28 | _ => List("sbt.internal.util.codec.JValueFormats") 29 | 30 | case "sbt.internal.bsp.BuildTargetIdentifier" => 31 | _ => List("sbt.internal.bsp.codec.BuildTargetIdentifierFormats") 32 | } 33 | 34 | /** Returns the list of formats required to encode the given `TpeRef`. */ 35 | val getFormats: Type => List[String] = 36 | CodecCodeGen.extensibleFormatsForType { 37 | case tpe: Type if sbtCodecs.isDefinedAt(tpe.removeTypeParameters.name) => 38 | sbtCodecs(tpe.removeTypeParameters.name)(tpe) 39 | case other => CodecCodeGen.formatsForType(other) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | object Dependencies { 4 | val scalaEnvVersion = Option(System.getenv("SCALA_VERSION")) 5 | val scala212 = scalaEnvVersion.filter(isScala212).getOrElse("2.12.20") 6 | val scala213 = scalaEnvVersion.filter(isScala213).getOrElse("2.13.16") 7 | val scala30 = scalaEnvVersion.filter(isScala30).getOrElse("3.0.2") 8 | val scala31Plus = scalaEnvVersion.filter(isScala31Plus).getOrElse("3.3.6") 9 | val scala34Plus = scalaEnvVersion.filter(isScala34Plus).getOrElse("3.7.0") 10 | val asmVersion = "9.8" 11 | val coursierVersion = "2.1.24" 12 | 13 | def isScala212(version: String): Boolean = version.startsWith("2.12") 14 | def isScala213(version: String): Boolean = version.startsWith("2.13") 15 | def isScala30(version: String): Boolean = version.startsWith("3.0") 16 | def isScala31Plus(version: String): Boolean = SemVer.matches(version) { case (3, 1 | 2 | 3, _) => true } 17 | def isScala34Plus(version: String): Boolean = SemVer.matches(version) { case (3, min, _) => min >= 4 } 18 | 19 | val asm = "org.ow2.asm" % "asm" % asmVersion 20 | val asmUtil = "org.ow2.asm" % "asm-util" % asmVersion 21 | 22 | def scalaCompiler(scalaVersion: String): ModuleID = 23 | SemVer(scalaVersion) match { 24 | case (3, _, _) => "org.scala-lang" %% "scala3-compiler" % scalaVersion 25 | case _ => "org.scala-lang" % "scala-compiler" % scalaVersion 26 | } 27 | 28 | def scalaReflect(scalaVersion: String): ModuleID = 29 | SemVer(scalaVersion) match { 30 | case (3, _, _) => "org.scala-lang" % "scala-reflect" % scala213 31 | case _ => "org.scala-lang" % "scala-reflect" % scalaVersion 32 | } 33 | 34 | val scalaParallelCollection = "org.scala-lang.modules" %% "scala-parallel-collections" % "1.2.0" 35 | val scalaCollectionCompat = "org.scala-lang.modules" %% "scala-collection-compat" % "2.13.0" 36 | val sbtTestAgent = "org.scala-sbt" % "test-agent" % "1.11.1" 37 | val scalaMeta = ("org.scalameta" %% "parsers" % "4.13.6").cross(CrossVersion.for3Use2_13) 38 | 39 | // test dependencies 40 | val munit = "org.scalameta" %% "munit" % "1.1.1" 41 | val coursier = "io.get-coursier" %% "coursier" % coursierVersion 42 | val coursierJvm = "io.get-coursier" %% "coursier-jvm" % coursierVersion 43 | } 44 | -------------------------------------------------------------------------------- /project/Developers.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | object Developers { 4 | val adpi2: Developer = Developer( 5 | "adpi2", 6 | "Adrien Piquerez", 7 | "adrien.piquerez@gmail.com", 8 | url("https://github.com/adpi2/") 9 | ) 10 | 11 | val ericpeters = Developer( 12 | "ericpeters", 13 | "Eric Peters", 14 | "eric@peters.org", 15 | url("https://github.com/er1c") 16 | ) 17 | 18 | val tdudzik = Developer( 19 | "tdudzik", 20 | "Tomasz Dudzik", 21 | "tdudzik@virtuslab.com", 22 | url("https://github.com/tdudzik") 23 | ) 24 | 25 | val tgodzik = Developer( 26 | "tgodzik", 27 | "Tomasz Godzik", 28 | "tgodzik@virtuslab.com", 29 | url("https://github.com/tgodzik") 30 | ) 31 | 32 | val list: List[Developer] = List(adpi2, ericpeters, tdudzik, tgodzik) 33 | } 34 | -------------------------------------------------------------------------------- /project/SemVer.scala: -------------------------------------------------------------------------------- 1 | object SemVer { 2 | def apply(version: String): (Int, Int, Int) = 3 | "(\\d+)\\.(\\d+)\\.(\\d+)(?:-.*)?".r.unapplySeq(version).map(xs => (xs(0).toInt, xs(1).toInt, xs(2).toInt)).get 4 | 5 | def matches(version: String)(f: PartialFunction[(Int, Int, Int), Boolean]): Boolean = 6 | f.lift(SemVer(version)).getOrElse(false) 7 | } 8 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.11.1 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // required for adopt@1.8 2 | addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") 3 | 4 | addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.7.0") 5 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.11.1") 6 | // force version to fix https://github.com/sbt/sbt-pgp/issues/199 7 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1") 8 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1") 9 | addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.11.0") 10 | --------------------------------------------------------------------------------