diff --git a/.azure-pipelines/publish-to-maven.yml b/.azure-pipelines/publish-to-maven.yml
new file mode 100644
index 000000000..a1ef0a204
--- /dev/null
+++ b/.azure-pipelines/publish-to-maven.yml
@@ -0,0 +1,104 @@
+name: $(Date:yyyyMMdd).$(Rev:r)
+resources:
+ repositories:
+ - repository: MicroBuildTemplate
+ type: git
+ name: 1ESPipelineTemplates/MicroBuildTemplate
+ ref: refs/tags/release
+trigger: none
+extends:
+ template: azure-pipelines/1ES.Official.Publish.yml@MicroBuildTemplate
+ parameters:
+ pool:
+ os: linux
+ name: 1ES_JavaTooling_Pool
+ image: 1ES_JavaTooling_Ubuntu-2004
+ sdl:
+ sourceAnalysisPool:
+ name: 1ES_JavaTooling_Pool
+ image: 1ES_JavaTooling_Windows_2022
+ os: windows
+ stages:
+ - stage: PublishToMaven
+ jobs:
+ - job: PublishToMaven
+ displayName: Maven Release job
+ templateContext:
+ type: releaseJob
+ isProduction: true
+ steps:
+ - task: DownloadBuildArtifacts@1
+ displayName: 'Download Jar Artifacts'
+ inputs:
+ buildType: specific
+ project: 'a4d27ce2-a42d-4b71-8eef-78cee9a9728e'
+ pipeline: 16486
+ downloadType: specific
+ extractTars: false
+ itemPattern: 'm2/**'
+ - script: |
+ echo "import public key"
+ echo $GPG_PUBLIC_B64 | base64 -d | gpg --import
+
+ echo "import secret key"
+ echo $GPG_SECRET_B64 | base64 -d | gpg --batch --passphrase $GPGPASS --import
+ displayName: 'import GPG keys'
+ env:
+ GPG_PUBLIC_B64: $(GPG_PUBLIC_B64)
+ GPG_SECRET_B64: $(GPG_SECRET_B64)
+ GPGPASS: $(GPGPASS)
+ - task: NodeTool@0
+ displayName: 'Use Node 20.x'
+ inputs:
+ versionSpec: 20.x
+ - script: |
+ cd $(System.ArtifactsDirectory)/m2
+ pluginJarFile=$(basename -- java-debug-parent/*.pom)
+
+ # remove .* from end
+ noExt=${pluginJarFile%.*}
+
+ # remove *- from start
+ export releaseVersion=${noExt##*-}
+ echo $releaseVersion
+
+ export artifactFolder=$(pwd .)
+ wget https://raw.githubusercontent.com/microsoft/java-debug/master/scripts/publishMaven.js
+
+ export GPG_TTY=$(tty)
+ node publishMaven.js -task gpg
+ displayName: 'sign artifacts'
+ env:
+ GPG_PUBLIC_B64: $(GPG_PUBLIC_B64)
+ GPG_SECRET_B64: $(GPG_SECRET_B64)
+ GPGPASS: $(GPGPASS)
+ NEXUS_OSSRHPASS: $(NEXUS_OSSRHPASS)
+ NEXUS_OSSRHUSER: $(NEXUS_OSSRHUSER)
+ NEXUS_STAGINGPROFILEID: $(NEXUS_STAGINGPROFILEID)
+ - template: MicroBuild.Publish.yml@MicroBuildTemplate
+ parameters:
+ intent: 'PackageDistribution'
+ contentType: 'Maven'
+ contentSource: 'Folder'
+ folderLocation: '$(System.ArtifactsDirectory)/m2/java-debug-parent'
+ waitForReleaseCompletion: true
+ owners: 'jinbwan@microsoft.com'
+ approvers: 'roml@microsoft.com'
+ - template: MicroBuild.Publish.yml@MicroBuildTemplate
+ parameters:
+ intent: 'PackageDistribution'
+ contentType: 'Maven'
+ contentSource: 'Folder'
+ folderLocation: '$(System.ArtifactsDirectory)/m2/com.microsoft.java.debug.core'
+ waitForReleaseCompletion: true
+ owners: 'jinbwan@microsoft.com'
+ approvers: 'roml@microsoft.com'
+ - template: MicroBuild.Publish.yml@MicroBuildTemplate
+ parameters:
+ intent: 'PackageDistribution'
+ contentType: 'Maven'
+ contentSource: 'Folder'
+ folderLocation: '$(System.ArtifactsDirectory)/m2/com.microsoft.java.debug.plugin'
+ waitForReleaseCompletion: true
+ owners: 'jinbwan@microsoft.com'
+ approvers: 'roml@microsoft.com'
\ No newline at end of file
diff --git a/.azure-pipelines/signjars-nightly.yml b/.azure-pipelines/signjars-nightly.yml
new file mode 100644
index 000000000..8b1e8d12a
--- /dev/null
+++ b/.azure-pipelines/signjars-nightly.yml
@@ -0,0 +1,138 @@
+name: $(Date:yyyyMMdd).$(Rev:r)
+variables:
+ - name: Codeql.Enabled
+ value: true
+schedules:
+ - cron: 0 5 * * 1,2,3,4,5
+ branches:
+ include:
+ - refs/heads/main
+resources:
+ repositories:
+ - repository: self
+ type: git
+ ref: refs/heads/main
+ - repository: 1esPipelines
+ type: git
+ name: 1ESPipelineTemplates/1ESPipelineTemplates
+ ref: refs/tags/release
+trigger: none
+extends:
+ template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines
+ parameters:
+ pool:
+ os: linux
+ name: 1ES_JavaTooling_Pool
+ image: 1ES_JavaTooling_Ubuntu-2004
+ sdl:
+ sourceAnalysisPool:
+ name: 1ES_JavaTooling_Pool
+ image: 1ES_JavaTooling_Windows_2022
+ os: windows
+ customBuildTags:
+ - MigrationTooling-mseng-VSJava-13474-Tool
+ stages:
+ - stage: Build
+ jobs:
+ - job: Job_1
+ displayName: Sign-Jars-Nightly
+ templateContext:
+ outputs:
+ - output: pipelineArtifact
+ artifactName: plugin
+ targetPath: $(Build.ArtifactStagingDirectory)
+ displayName: "Publish Artifact: plugin"
+ steps:
+ - checkout: self
+ fetchTags: true
+ - task: UseDotNet@2
+ displayName: 'Use .NET Core 3.1.x'
+ inputs:
+ packageType: 'sdk'
+ version: '3.1.x'
+ - task: UseDotNet@2
+ displayName: 'Use .NET Core 8.0.x'
+ inputs:
+ packageType: 'sdk'
+ version: '8.0.x'
+ - task: MicroBuildSigningPlugin@4
+ displayName: 'Install Signing Plugin'
+ inputs:
+ signType: real
+ azureSubscription: 'MicroBuild Signing Task (MSEng)'
+ useEsrpCli: true
+ ConnectedPMEServiceName: 0e38ce24-f885-4c86-b997-5887b97a1899
+ feedSource: 'https://mseng.pkgs.visualstudio.com/DefaultCollection/_packaging/MicroBuildToolset/nuget/v3/index.json'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)'
+ - task: JavaToolInstaller@0
+ displayName: Use Java 21
+ inputs:
+ versionSpec: "21"
+ jdkArchitectureOption: x64
+ jdkSourceOption: PreInstalled
+ - task: CmdLine@2
+ displayName: Parse the release version from pom.xml
+ inputs:
+ script: |-
+ #!/bin/bash
+
+ sudo apt-get install xmlstarlet
+ xmlstarlet --version
+ RELEASE_VERSION=$(xmlstarlet sel -t -v "/_:project/_:version" pom.xml)
+ echo $RELEASE_VERSION
+ echo "##vso[task.setvariable variable=RELEASE_VERSION]$RELEASE_VERSION"
+ - task: CmdLine@2
+ displayName: Build core.jar
+ inputs:
+ script: |
+ ./mvnw clean install -f com.microsoft.java.debug.core/pom.xml -Dmaven.repo.local=./.repository
+
+ mkdir -p jars
+ mv .repository/com/microsoft/java/com.microsoft.java.debug.core/$RELEASE_VERSION/com.microsoft.java.debug.core*.jar jars/
+ - task: CmdLine@2
+ displayName: Sign core jars
+ inputs:
+ script: |
+ files=$(find . -type f -name "com.microsoft.java.debug.core*.jar")
+ for file in $files; do
+ fileName=$(basename "$file")
+ dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$fileName" /certs:100010171
+ done
+ workingDirectory: 'jars'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ - task: CmdLine@2
+ displayName: install signed core.jar
+ inputs:
+ script: cp jars/com.microsoft.java.debug.core*.jar .repository/com/microsoft/java/com.microsoft.java.debug.core/$RELEASE_VERSION/
+ - task: CmdLine@2
+ displayName: Build plugin.jar
+ inputs:
+ script: |-
+ ./mvnw clean install -N -f pom.xml -Dmaven.repo.local=./.repository
+ ./mvnw clean install -f com.microsoft.java.debug.target/pom.xml -Dmaven.repo.local=./.repository
+ ./mvnw clean install -f com.microsoft.java.debug.plugin/pom.xml -Dmaven.repo.local=./.repository
+
+ mkdir -p jars
+ mv .repository/com/microsoft/java/com.microsoft.java.debug.plugin/$RELEASE_VERSION/com.microsoft.java.debug.plugin*.jar jars/
+ - task: CmdLine@2
+ displayName: Sign plugin jars
+ inputs:
+ script: |
+ files=$(find . -type f -name "com.microsoft.java.debug.plugin*.jar")
+ for file in $files; do
+ fileName=$(basename "$file")
+ dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$fileName" /certs:100010171
+ done
+ workingDirectory: 'jars'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ - task: CopyFiles@2
+ displayName: "Copy plugin.jar to: $(Build.ArtifactStagingDirectory)"
+ inputs:
+ Contents: |+
+ jars/com.microsoft.java.debug.plugin*.jar
+
+ TargetFolder: $(Build.ArtifactStagingDirectory)
diff --git a/.azure-pipelines/signjars-rc.yml b/.azure-pipelines/signjars-rc.yml
new file mode 100644
index 000000000..e87444603
--- /dev/null
+++ b/.azure-pipelines/signjars-rc.yml
@@ -0,0 +1,169 @@
+name: $(Date:yyyyMMdd).$(Rev:r)
+variables:
+ - name: Codeql.Enabled
+ value: true
+resources:
+ repositories:
+ - repository: self
+ type: git
+ ref: refs/heads/main
+ - repository: 1esPipelines
+ type: git
+ name: 1ESPipelineTemplates/1ESPipelineTemplates
+ ref: refs/tags/release
+trigger: none
+extends:
+ template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines
+ parameters:
+ pool:
+ os: linux
+ name: 1ES_JavaTooling_Pool
+ image: 1ES_JavaTooling_Ubuntu-2004
+ sdl:
+ sourceAnalysisPool:
+ name: 1ES_JavaTooling_Pool
+ image: 1ES_JavaTooling_Windows_2022
+ os: windows
+ customBuildTags:
+ - MigrationTooling-mseng-VSJava-9151-Tool
+ stages:
+ - stage: Build
+ jobs:
+ - job: Job_1
+ displayName: Sign-Jars-RC
+ templateContext:
+ outputs:
+ - output: pipelineArtifact
+ artifactName: m2
+ targetPath: $(Build.ArtifactStagingDirectory)/m2
+ displayName: "Publish Artifact: m2"
+ steps:
+ - checkout: self
+ fetchTags: true
+ - task: UseDotNet@2
+ displayName: 'Use .NET Core 3.1.x'
+ inputs:
+ packageType: 'sdk'
+ version: '3.1.x'
+ - task: UseDotNet@2
+ displayName: 'Use .NET Core 8.0.x'
+ inputs:
+ packageType: 'sdk'
+ version: '8.0.x'
+ - task: MicroBuildSigningPlugin@4
+ displayName: 'Install Signing Plugin'
+ inputs:
+ signType: real
+ azureSubscription: 'MicroBuild Signing Task (MSEng)'
+ useEsrpCli: true
+ ConnectedPMEServiceName: 0e38ce24-f885-4c86-b997-5887b97a1899
+ feedSource: 'https://mseng.pkgs.visualstudio.com/DefaultCollection/_packaging/MicroBuildToolset/nuget/v3/index.json'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)'
+ - task: JavaToolInstaller@0
+ displayName: Use Java 21
+ inputs:
+ versionSpec: "21"
+ jdkArchitectureOption: x64
+ jdkSourceOption: PreInstalled
+ - task: CmdLine@2
+ displayName: Parse the release version from pom.xml
+ inputs:
+ script: |-
+ #!/bin/bash
+
+ sudo apt-get install xmlstarlet
+ xmlstarlet --version
+ RELEASE_VERSION=$(xmlstarlet sel -t -v "/_:project/_:version" pom.xml)
+ echo $RELEASE_VERSION
+ echo "##vso[task.setvariable variable=RELEASE_VERSION]$RELEASE_VERSION"
+ - task: CmdLine@2
+ displayName: Build core.jar
+ inputs:
+ script: |
+ ./mvnw -N clean install -Dmaven.repo.local=./.repository
+
+ ./mvnw clean install -f com.microsoft.java.debug.core/pom.xml -Dmaven.repo.local=./.repository
+
+ mkdir -p jars
+ mv .repository/com/microsoft/java/com.microsoft.java.debug.core/$RELEASE_VERSION/com.microsoft.java.debug.core*.jar jars/
+ - task: CmdLine@2
+ displayName: Sign core jars
+ inputs:
+ script: |
+ files=$(find . -type f -name "com.microsoft.java.debug.core*.jar")
+ for file in $files; do
+ fileName=$(basename "$file")
+ dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$fileName" /certs:100010171
+ done
+ workingDirectory: 'jars'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ - task: CmdLine@2
+ displayName: install signed core.jar
+ inputs:
+ script: cp jars/com.microsoft.java.debug.core*.jar .repository/com/microsoft/java/com.microsoft.java.debug.core/$RELEASE_VERSION/
+ - task: CmdLine@2
+ displayName: Build plugin.jar
+ inputs:
+ script: |-
+ ./mvnw clean install -f com.microsoft.java.debug.target/pom.xml -Dmaven.repo.local=./.repository
+ ./mvnw clean install -f com.microsoft.java.debug.plugin/pom.xml -Dmaven.repo.local=./.repository
+
+ mkdir -p jars
+ mv .repository/com/microsoft/java/com.microsoft.java.debug.plugin/$RELEASE_VERSION/com.microsoft.java.debug.plugin*.jar jars/
+ - task: CmdLine@2
+ displayName: Sign plugin jars
+ inputs:
+ script: |
+ files=$(find . -type f -name "com.microsoft.java.debug.plugin*.jar")
+ for file in $files; do
+ fileName=$(basename "$file")
+ dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$fileName" /certs:100010171
+ done
+ workingDirectory: 'jars'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ - task: CmdLine@2
+ displayName: install signed plugin.jar
+ inputs:
+ script: cp jars/com.microsoft.java.debug.plugin*.jar .repository/com/microsoft/java/com.microsoft.java.debug.plugin/$RELEASE_VERSION/
+ - task: CmdLine@2
+ displayName: build m2 artifacts
+ inputs:
+ script: |
+ ./mvnw source:jar -f com.microsoft.java.debug.core/pom.xml -Dmaven.repo.local=./.repository
+ ./mvnw javadoc:jar -f com.microsoft.java.debug.core/pom.xml -Ddoclint=none -Dmaven.repo.local=./.repository
+
+ ./mvnw source:jar -f com.microsoft.java.debug.plugin/pom.xml -Dmaven.repo.local=./.repository
+ ./mvnw javadoc:jar -f com.microsoft.java.debug.plugin/pom.xml -Ddoclint=none -Dmaven.repo.local=./.repository
+
+ mkdir -p m2/java-debug-parent
+ cp pom.xml m2/java-debug-parent/java-debug-parent-$RELEASE_VERSION.pom
+
+ mkdir -p m2/com.microsoft.java.debug.core
+ cp com.microsoft.java.debug.core/target/com.microsoft.java.debug.core*.jar m2/com.microsoft.java.debug.core
+ cp com.microsoft.java.debug.core/pom.xml m2/com.microsoft.java.debug.core/com.microsoft.java.debug.core-$RELEASE_VERSION.pom
+
+ mkdir -p m2/com.microsoft.java.debug.plugin
+ cp com.microsoft.java.debug.plugin/target/com.microsoft.java.debug.plugin*.jar m2/com.microsoft.java.debug.plugin
+ cp com.microsoft.java.debug.plugin/pom.xml m2/com.microsoft.java.debug.plugin/com.microsoft.java.debug.plugin-$RELEASE_VERSION.pom
+ - task: CmdLine@2
+ displayName: Sign m2 jars
+ inputs:
+ script: |
+ files=$(find . -type f -name "*.jar")
+ for file in $files; do
+ # fileName=$(basename "$file")
+ dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$file" /certs:100010171
+ done
+ workingDirectory: 'm2'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ - task: CopyFiles@2
+ displayName: "Copy m2 to: $(Build.ArtifactStagingDirectory)"
+ inputs:
+ Contents: |+
+ m2/**
+ TargetFolder: $(Build.ArtifactStagingDirectory)
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 3f8625455..9d7579633 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1 +1 @@
-* @testforstephen @jdneo @Eskibear @CsCherrYY
\ No newline at end of file
+* @testforstephen @jdneo @chagong @wenytang-ms
diff --git a/.github/llms.md b/.github/llms.md
new file mode 100644
index 000000000..55d69b238
--- /dev/null
+++ b/.github/llms.md
@@ -0,0 +1,38 @@
+# Extension Pack for Java
+Extension Pack for Java is a collection of popular extensions that can help write, test and debug Java applications in Visual Studio Code. By installing Extension Pack for Java, the following extensions are installed:
+
+- [📦 Language Support for Java™ by Red Hat ](https://marketplace.visualstudio.com/items?itemName=redhat.java)
+ - Code Navigation
+ - Auto Completion
+ - Refactoring
+ - Code Snippets
+- [📦 Debugger for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-debug)
+ - Debugging
+- [📦 Test Runner for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-test)
+ - Run & Debug JUnit/TestNG Test Cases
+- [📦 Maven for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-maven)
+ - Project Scaffolding
+ - Custom Goals
+- [📦 Gradle for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-gradle)
+ - View Gradle tasks and project dependencies
+ - Gradle file authoring
+ - Import Gradle projects via [Gradle Build Server](https://github.com/microsoft/build-server-for-gradle)
+- [📦 Project Manager for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-dependency)
+ - Manage Java projects, referenced libraries, resource files, packages, classes, and class members
+- [📦 Visual Studio IntelliCode](https://marketplace.visualstudio.com/items?itemName=VisualStudioExptTeam.vscodeintellicode)
+ - AI-assisted development
+ - Completion list ranked by AI
+
+## Label
+When labeling an issue, follow the rules below per label category:
+### General Rules
+- Analyze if the issue is related with the scope of using extensions for Java development. If not, STOP labelling IMMEDIATELY.
+- Assign label per category.
+- If a category is not applicable or you're unsure, you may skip it.
+- Do not assign multiple labels within the same category, unless explicitly allowed as an exception.
+
+### Issue Type Labels
+- [bug]: Primary label for real bug issues
+- [enhancement]: Primary label for enhancement issues
+- [documentation]: Primary label for documentation issues
+- [question]: Primary label for question issues
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 41c85cb62..553d95a9a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,80 +12,83 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v5
- - name: Set up JDK 17
- uses: actions/setup-java@v1
- with:
- java-version: '17'
+ - name: Set up JDK 21
+ uses: actions/setup-java@v5
+ with:
+ java-version: '21'
+ distribution: 'temurin'
- - name: Cache local Maven repository
- uses: actions/cache@v2
- with:
- path: ~/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-maven-
+ - name: Cache local Maven repository
+ uses: actions/cache@v4
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
- - name: Verify
- run: ./mvnw clean verify
+ - name: Verify
+ run: ./mvnw clean verify -U
- - name: Checkstyle
- run: ./mvnw checkstyle:check
+ - name: Checkstyle
+ run: ./mvnw checkstyle:check
windows:
name: Windows
runs-on: windows-latest
timeout-minutes: 30
steps:
- - name: Set git to use LF
- run: |
- git config --global core.autocrlf false
- git config --global core.eol lf
+ - name: Set git to use LF
+ run: |
+ git config --global core.autocrlf false
+ git config --global core.eol lf
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v5
- - name: Set up JDK 17
- uses: actions/setup-java@v1
- with:
- java-version: '17'
+ - name: Set up JDK 21
+ uses: actions/setup-java@v5
+ with:
+ java-version: '21'
+ distribution: 'temurin'
- - name: Cache local Maven repository
- uses: actions/cache@v2
- with:
- path: $HOME/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-maven-
+ - name: Cache local Maven repository
+ uses: actions/cache@v4
+ with:
+ path: $HOME/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
- - name: Verify
- run: ./mvnw.cmd clean verify
+ - name: Verify
+ run: ./mvnw.cmd clean verify
- - name: Checkstyle
- run: ./mvnw.cmd checkstyle:check
+ - name: Checkstyle
+ run: ./mvnw.cmd checkstyle:check
darwin:
name: macOS
runs-on: macos-latest
timeout-minutes: 30
steps:
- - uses: actions/checkout@v2
-
- - name: Set up JDK 17
- uses: actions/setup-java@v1
- with:
- java-version: '17'
-
- - name: Cache local Maven repository
- uses: actions/cache@v2
- with:
- path: ~/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-maven-
-
- - name: Verify
- run: ./mvnw clean verify
-
- - name: Checkstyle
- run: ./mvnw checkstyle:check
+ - uses: actions/checkout@v5
+
+ - name: Set up JDK 21
+ uses: actions/setup-java@v5
+ with:
+ java-version: '21'
+ distribution: 'temurin'
+
+ - name: Cache local Maven repository
+ uses: actions/cache@v4
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+ - name: Verify
+ run: ./mvnw clean verify -U
+
+ - name: Checkstyle
+ run: ./mvnw checkstyle:check
diff --git a/.github/workflows/triage-agent.yml b/.github/workflows/triage-agent.yml
new file mode 100644
index 000000000..f1df6e117
--- /dev/null
+++ b/.github/workflows/triage-agent.yml
@@ -0,0 +1,125 @@
+name: AI Triage
+on:
+ issues:
+ types: [opened]
+ workflow_dispatch:
+ inputs:
+ issue_number:
+ description: 'Issue number to triage (manual run). e.g. 123'
+ required: true
+
+run-name: >-
+ AI Triage for Issue #${{ github.event.issue.number || github.event.inputs.issue_number }}
+
+permissions:
+ issues: write
+ contents: read
+
+jobs:
+ label_and_comment:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ - name: Get issue data
+ id: get_issue
+ uses: actions/github-script@v6
+ with:
+ script: |
+ const eventName = context.eventName;
+ let issue;
+ if (eventName === 'workflow_dispatch') {
+ const inputs = context.payload.inputs || {};
+ const issueNumber = inputs.issue_number || inputs.issueNumber;
+ if (!issueNumber) core.setFailed('Input issue_number is required for manual run.');
+ const { data } = await github.rest.issues.get({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: parseInt(issueNumber, 10),
+ });
+ issue = data;
+ } else if (context.payload.issue) {
+ issue = context.payload.issue;
+ } else {
+ core.setFailed('No issue information found in the event payload.');
+ }
+ core.setOutput('id', String(issue.number));
+ core.setOutput('user', String((issue.user && issue.user.login) || ''));
+ core.setOutput('title', String(issue.title || ''));
+ core.setOutput('body', String(issue.body || ''));
+ const labelNames = (issue.labels || []).map(label => label.name);
+ core.setOutput('labels', JSON.stringify(labelNames));
+
+ - name: Call Azure Function
+ id: call_azure_function
+ env:
+ PAYLOAD: >-
+ {
+ "authToken": "${{ secrets.GITHUB_TOKEN }}",
+ "repoId": "microsoft/java-debug",
+ "issueData": {
+ "id": ${{ steps.get_issue.outputs.id }},
+ "user": ${{ toJson(steps.get_issue.outputs.user) }},
+ "title": ${{ toJson(steps.get_issue.outputs.title) }},
+ "body": ${{ toJson(steps.get_issue.outputs.body) }},
+ "labels": ${{ steps.get_issue.outputs.labels }}
+ },
+ "mode": "DirectUpdate"
+ }
+
+ run: |
+ # Make the HTTP request with improved error handling and timeouts
+ echo "Making request to triage agent..."
+
+ # Add timeout handling and better error detection
+ set +e # Don't exit on curl failure
+ response=$(timeout ${{ vars.TRIAGE_AGENT_TIMEOUT }} curl \
+ --max-time 0 \
+ --connect-timeout 30 \
+ --fail-with-body \
+ --silent \
+ --show-error \
+ --write-out "HTTPSTATUS:%{http_code}" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data "$PAYLOAD" \
+ ${{ secrets.TRIAGE_FUNCTION_LINK }} 2>&1)
+
+ curl_exit_code=$?
+ set -e # Re-enable exit on error
+
+ echo "Curl exit code: $curl_exit_code"
+
+ # Check if curl command timed out or failed
+ if [ $curl_exit_code -eq 124 ]; then
+ echo "❌ Request timed out after 650 seconds"
+ exit 1
+ elif [ $curl_exit_code -ne 0 ]; then
+ echo "❌ Curl command failed with exit code: $curl_exit_code"
+ echo "Response: $response"
+ exit 1
+ fi
+
+ # Extract HTTP status code and response body
+ http_code=$(echo "$response" | grep -o "HTTPSTATUS:[0-9]*" | cut -d: -f2)
+ response_body=$(echo "$response" | sed 's/HTTPSTATUS:[0-9]*$//')
+
+ echo "HTTP Status Code: $http_code"
+
+ # Validate HTTP status code
+ if [ -z "$http_code" ]; then
+ echo "❌ Failed to extract HTTP status code from response"
+ echo "Raw response: $response"
+ exit 1
+ fi
+
+ # Check if the request was successful
+ if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
+ echo "✅ Azure Function call succeeded"
+ else
+ echo "❌ Azure Function call failed with status code: $http_code"
+ echo "Response: $response_body"
+ exit 1
+ fi
\ No newline at end of file
diff --git a/.github/workflows/triage-all-open-issues.yml b/.github/workflows/triage-all-open-issues.yml
new file mode 100644
index 000000000..092f4ef70
--- /dev/null
+++ b/.github/workflows/triage-all-open-issues.yml
@@ -0,0 +1,145 @@
+name: AI Triage - Process All Open Issues
+on:
+ workflow_dispatch:
+ inputs:
+ dry_run:
+ description: 'Dry run mode - only list issues without processing'
+ required: false
+ default: false
+ type: boolean
+ max_issues:
+ description: 'Maximum number of issues to process (0 = all)'
+ required: false
+ default: '0'
+ type: string
+
+permissions:
+ issues: write
+ contents: read
+ actions: write
+
+jobs:
+ get_open_issues:
+ runs-on: ubuntu-latest
+ outputs:
+ issue_numbers: ${{ steps.get_issues.outputs.issue_numbers }}
+ total_count: ${{ steps.get_issues.outputs.total_count }}
+
+ steps:
+ - name: Get all open issues
+ id: get_issues
+ uses: actions/github-script@v6
+ with:
+ script: |
+ // Use Search API to filter issues at API level
+ const { data } = await github.rest.search.issuesAndPullRequests({
+ q: `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open -label:ai-triaged -label:invalid`,
+ sort: 'created',
+ order: 'asc',
+ per_page: 100
+ });
+
+ const actualIssues = data.items;
+
+ let issuesToProcess = actualIssues;
+ const maxIssues = parseInt('${{ inputs.max_issues }}' || '0');
+
+ if (maxIssues > 0 && actualIssues.length > maxIssues) {
+ issuesToProcess = actualIssues.slice(0, maxIssues);
+ console.log(`Limiting to first ${maxIssues} issues out of ${actualIssues.length} total`);
+ }
+
+ const issueNumbers = issuesToProcess.map(issue => issue.number);
+ const totalCount = issuesToProcess.length;
+
+ console.log(`Found ${actualIssues.length} open issues, processing ${totalCount}:`);
+ issuesToProcess.forEach(issue => {
+ console.log(` #${issue.number}: ${issue.title}`);
+ });
+
+ core.setOutput('issue_numbers', JSON.stringify(issueNumbers));
+ core.setOutput('total_count', totalCount);
+
+ process_issues:
+ runs-on: ubuntu-latest
+ needs: get_open_issues
+ if: needs.get_open_issues.outputs.total_count > 0
+
+ strategy:
+ # Process issues one by one (max-parallel: 1)
+ max-parallel: 1
+ matrix:
+ issue_number: ${{ fromJSON(needs.get_open_issues.outputs.issue_numbers) }}
+
+ steps:
+ - name: Log current issue being processed
+ run: |
+ echo "🔄 Processing issue #${{ matrix.issue_number }}"
+ echo "Total issues to process: ${{ needs.get_open_issues.outputs.total_count }}"
+
+ - name: Check if dry run mode
+ if: inputs.dry_run == true
+ run: |
+ echo "🔍 DRY RUN MODE: Would process issue #${{ matrix.issue_number }}"
+ echo "Skipping actual triage processing"
+
+ - name: Trigger triage workflow for issue
+ if: inputs.dry_run != true
+ uses: actions/github-script@v6
+ with:
+ script: |
+ const issueNumber = '${{ matrix.issue_number }}';
+
+ try {
+ console.log(`Triggering triage workflow for issue #${issueNumber}`);
+
+ const response = await github.rest.actions.createWorkflowDispatch({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ workflow_id: 'triage-agent.yml',
+ ref: 'main',
+ inputs: {
+ issue_number: issueNumber
+ }
+ });
+
+ console.log(`✅ Successfully triggered triage workflow for issue #${issueNumber}`);
+
+ } catch (error) {
+ console.error(`❌ Failed to trigger triage workflow for issue #${issueNumber}:`, error);
+ core.setFailed(`Failed to process issue #${issueNumber}: ${error.message}`);
+ }
+
+ - name: Wait for workflow completion
+ if: inputs.dry_run != true
+ run: |
+ echo "⏳ Waiting for triage workflow to complete for issue #${{ matrix.issue_number }}..."
+ echo "Timeout: ${{ vars.TRIAGE_AGENT_TIMEOUT }} seconds"
+ sleep ${{ vars.TRIAGE_AGENT_TIMEOUT }} # Wait for triage workflow completion
+
+ summary:
+ runs-on: ubuntu-latest
+ needs: [get_open_issues, process_issues]
+ if: always()
+
+ steps:
+ - name: Print summary
+ run: |
+ echo "## Triage Processing Summary"
+ echo "Total open issues found: ${{ needs.get_open_issues.outputs.total_count }}"
+
+ if [ "${{ inputs.dry_run }}" == "true" ]; then
+ echo "Mode: DRY RUN (no actual processing performed)"
+ else
+ echo "Mode: FULL PROCESSING"
+ fi
+
+ if [ "${{ needs.process_issues.result }}" == "success" ]; then
+ echo "✅ All issues processed successfully"
+ elif [ "${{ needs.process_issues.result }}" == "failure" ]; then
+ echo "❌ Some issues failed to process"
+ elif [ "${{ needs.process_issues.result }}" == "skipped" ]; then
+ echo "⏭️ Processing was skipped (no open issues found)"
+ else
+ echo "⚠️ Processing completed with status: ${{ needs.process_issues.result }}"
+ fi
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
deleted file mode 100644
index 41c70a7e0..000000000
Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index 56bb0164e..44f3cf2c1 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1 +1,2 @@
-distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip
\ No newline at end of file
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..e138ec5d6
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,41 @@
+
+
+## Security
+
+Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
+
+If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
+
+## Reporting Security Issues
+
+**Please do not report security vulnerabilities through public GitHub issues.**
+
+Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
+
+If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
+
+You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
+
+Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
+
+ * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
+ * Full paths of source file(s) related to the manifestation of the issue
+ * The location of the affected source code (tag/branch/commit or direct URL)
+ * Any special configuration required to reproduce the issue
+ * Step-by-step instructions to reproduce the issue
+ * Proof-of-concept or exploit code (if possible)
+ * Impact of the issue, including how an attacker might exploit the issue
+
+This information will help us triage your report more quickly.
+
+If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
+
+## Preferred Languages
+
+We prefer all communications to be in English.
+
+## Policy
+
+Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
+
+
diff --git a/com.microsoft.java.debug.core/.classpath b/com.microsoft.java.debug.core/.classpath
index 9ba41a249..b6fe6b96c 100644
--- a/com.microsoft.java.debug.core/.classpath
+++ b/com.microsoft.java.debug.core/.classpath
@@ -13,7 +13,7 @@
-
+
diff --git a/com.microsoft.java.debug.core/pom.xml b/com.microsoft.java.debug.core/pom.xml
index f253b45da..8bfdb7292 100644
--- a/com.microsoft.java.debug.core/pom.xml
+++ b/com.microsoft.java.debug.core/pom.xml
@@ -5,7 +5,7 @@
com.microsoft.java
java-debug-parent
- 0.44.0
+ 0.53.2
com.microsoft.java.debug.core
jar
@@ -42,7 +42,7 @@
org.apache.commons
commons-lang3
- 3.6
+ 3.18.0
com.google.code.gson
@@ -62,7 +62,7 @@
commons-io
commons-io
- 2.11.0
+ 2.14.0
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java
index dfdbad4bb..82859b268 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java
@@ -325,7 +325,8 @@ private Location findMethodLocaiton(ReferenceType refType, String methodName, St
for (Method method : methods) {
if (!method.isAbstract() && !method.isNative()
&& methodName.equals(method.name())
- && (methodSiguature.equals(method.genericSignature()) || methodSiguature.equals(method.signature()))) {
+ && (methodSiguature.equals(method.genericSignature()) || methodSiguature.equals(method.signature())
+ || toNoneGeneric(methodSiguature).equals(method.signature()))) {
location = method.location();
break;
}
@@ -334,6 +335,28 @@ private Location findMethodLocaiton(ReferenceType refType, String methodName, St
return location;
}
+ static String toNoneGeneric(String genericSig) {
+ StringBuilder builder = new StringBuilder();
+ boolean append = true;
+ int depth = 0;
+ char[] chars = genericSig.toCharArray();
+ for (int i = 0; i < chars.length; i++) {
+ char c = chars[i];
+ if (c == '<') {
+ depth++;
+ append = (depth == 0);
+ }
+ if (append) {
+ builder.append(c);
+ }
+ if (c == '>') {
+ depth--;
+ append = (depth == 0);
+ }
+ }
+ return builder.toString();
+ }
+
private List findLocaitonsOfLine(Method method, int lineNumber) {
try {
return method.locationsOfLine(lineNumber);
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java
index 1c7990f16..38a234fa9 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java
@@ -16,16 +16,26 @@
import java.util.ArrayList;
import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+
import com.sun.jdi.ObjectCollectedException;
+import com.sun.jdi.ReferenceType;
import com.sun.jdi.ThreadReference;
+import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.VirtualMachine;
+import com.sun.jdi.event.ClassPrepareEvent;
+import com.sun.jdi.request.ClassPrepareRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.ExceptionRequest;
+import io.reactivex.disposables.Disposable;
+
public class DebugSession implements IDebugSession {
private VirtualMachine vm;
private EventHub eventHub = new EventHub();
+ private List eventRequests = new ArrayList<>();
+ private List subscriptions = new ArrayList<>();
public DebugSession(VirtualMachine virtualMachine) {
vm = virtualMachine;
@@ -109,7 +119,9 @@ public void detach() {
@Override
public void terminate() {
- if (vm.process() == null || vm.process().isAlive()) {
+ if (vm.process() != null && vm.process().isAlive()) {
+ vm.process().destroy();
+ } else if (vm.process() == null || vm.process().isAlive()) {
vm.exit(0);
}
}
@@ -136,9 +148,27 @@ public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught
@Override
public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters) {
+ setExceptionBreakpoints(notifyCaught, notifyUncaught, null, classFilters, classExclusionFilters);
+ }
+
+ @Override
+ public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] exceptionTypes,
+ String[] classFilters, String[] classExclusionFilters) {
EventRequestManager manager = vm.eventRequestManager();
- ArrayList legacy = new ArrayList<>(manager.exceptionRequests());
- manager.deleteEventRequests(legacy);
+
+ try {
+ ArrayList legacy = new ArrayList<>(manager.exceptionRequests());
+ manager.deleteEventRequests(legacy);
+ manager.deleteEventRequests(eventRequests);
+ } catch (VMDisconnectedException ex) {
+ // ignore since removing breakpoints is meaningless when JVM is terminated.
+ }
+ subscriptions.forEach(subscription -> {
+ subscription.dispose();
+ });
+ subscriptions.clear();
+ eventRequests.clear();
+
// When no exception breakpoints are requested, no need to create an empty exception request.
if (notifyCaught || notifyUncaught) {
// from: https://www.javatips.net/api/REPLmode-master/src/jm/mode/replmode/REPLRunner.java
@@ -153,20 +183,60 @@ public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught
// a thread to be available, and queries it by calling allThreads().
// See org.eclipse.debug.jdi.tests.AbstractJDITest for the example.
- // get only the uncaught exceptions
- ExceptionRequest request = manager.createExceptionRequest(null, notifyCaught, notifyUncaught);
- request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
- if (classFilters != null) {
- for (String classFilter : classFilters) {
- request.addClassFilter(classFilter);
+ if (exceptionTypes == null || exceptionTypes.length == 0) {
+ ExceptionRequest request = manager.createExceptionRequest(null, notifyCaught, notifyUncaught);
+ request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
+ if (classFilters != null) {
+ for (String classFilter : classFilters) {
+ request.addClassFilter(classFilter);
+ }
+ }
+ if (classExclusionFilters != null) {
+ for (String exclusionFilter : classExclusionFilters) {
+ request.addClassExclusionFilter(exclusionFilter);
+ }
}
+ request.enable();
+ return;
}
- if (classExclusionFilters != null) {
- for (String exclusionFilter : classExclusionFilters) {
- request.addClassExclusionFilter(exclusionFilter);
+
+ for (String exceptionType : exceptionTypes) {
+ if (StringUtils.isBlank(exceptionType)) {
+ continue;
+ }
+
+ // register exception breakpoint in the future loaded classes.
+ ClassPrepareRequest classPrepareRequest = manager.createClassPrepareRequest();
+ classPrepareRequest.addClassFilter(exceptionType);
+ classPrepareRequest.enable();
+ eventRequests.add(classPrepareRequest);
+
+ Disposable subscription = eventHub.events()
+ .filter(debugEvent -> debugEvent.event instanceof ClassPrepareEvent
+ && eventRequests.contains(debugEvent.event.request()))
+ .subscribe(debugEvent -> {
+ ClassPrepareEvent event = (ClassPrepareEvent) debugEvent.event;
+ createExceptionBreakpoint(event.referenceType(), notifyCaught, notifyUncaught, classFilters, classExclusionFilters);
+ });
+ subscriptions.add(subscription);
+
+ // register exception breakpoint in the loaded classes.
+ for (ReferenceType refType : vm.classesByName(exceptionType)) {
+ createExceptionBreakpoint(refType, notifyCaught, notifyUncaught, classFilters, classExclusionFilters);
}
}
- request.enable();
+ }
+ }
+
+ @Override
+ public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] exceptionTypes,
+ String[] classFilters, String[] classExclusionFilters, boolean async) {
+ if (async) {
+ AsyncJdwpUtils.runAsync(() -> {
+ setExceptionBreakpoints(notifyCaught, notifyUncaught, exceptionTypes, classFilters, classExclusionFilters);
+ });
+ } else {
+ setExceptionBreakpoints(notifyCaught, notifyUncaught, exceptionTypes, classFilters, classExclusionFilters);
}
}
@@ -195,4 +265,22 @@ public IMethodBreakpoint createFunctionBreakpoint(String className, String funct
int hitCount) {
return new MethodBreakpoint(vm, this.getEventHub(), className, functionName, condition, hitCount);
}
+
+ private void createExceptionBreakpoint(ReferenceType refType, boolean notifyCaught, boolean notifyUncaught,
+ String[] classFilters, String[] classExclusionFilters) {
+ EventRequestManager manager = vm.eventRequestManager();
+ ExceptionRequest request = manager.createExceptionRequest(refType, notifyCaught, notifyUncaught);
+ request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
+ if (classFilters != null) {
+ for (String classFilter : classFilters) {
+ request.addClassFilter(classFilter);
+ }
+ }
+ if (classExclusionFilters != null) {
+ for (String exclusionFilter : classExclusionFilters) {
+ request.addClassExclusionFilter(exclusionFilter);
+ }
+ }
+ request.enable();
+ }
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java
index f59f10043..0a3e05ec8 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java
@@ -19,7 +19,7 @@
import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.SerializedName;
import com.microsoft.java.debug.core.protocol.JsonUtils;
-import com.microsoft.java.debug.core.protocol.Requests.ClassFilters;
+import com.microsoft.java.debug.core.protocol.Requests.ExceptionFilters;
import com.microsoft.java.debug.core.protocol.Requests.StepFilters;
public final class DebugSettings {
@@ -39,11 +39,12 @@ public final class DebugSettings {
public String javaHome;
public HotCodeReplace hotCodeReplace = HotCodeReplace.MANUAL;
public StepFilters stepFilters = new StepFilters();
- public ClassFilters exceptionFilters = new ClassFilters();
+ public ExceptionFilters exceptionFilters = new ExceptionFilters();
public boolean exceptionFiltersUpdated = false;
public int limitOfVariablesPerJdwpRequest = 100;
public int jdwpRequestTimeout = 3000;
public AsyncMode asyncJDWP = AsyncMode.OFF;
+ public Switch debugSupportOnDecompiledSource = Switch.OFF;
public static DebugSettings getCurrent() {
return current;
@@ -97,6 +98,13 @@ public static enum AsyncMode {
OFF
}
+ public static enum Switch {
+ @SerializedName("on")
+ ON,
+ @SerializedName("off")
+ OFF
+ }
+
public static interface IDebugSettingChangeListener {
public void update(DebugSettings oldSettings, DebugSettings newSettings);
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java
index 4e4078c87..6cc3f3a46 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java
@@ -38,6 +38,11 @@ public interface IDebugSession {
void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters);
+ void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] exceptionTypes, String[] classFilters, String[] classExclusionFilters);
+
+ void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] exceptionTypes, String[] classFilters, String[] classExclusionFilters,
+ boolean async);
+
IMethodBreakpoint createFunctionBreakpoint(String className, String functionName, String condition, int hitCount);
Process process();
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/JavaBreakpointLocation.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/JavaBreakpointLocation.java
index 820e80c9b..3dbc1a24d 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/JavaBreakpointLocation.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/JavaBreakpointLocation.java
@@ -17,7 +17,11 @@
public class JavaBreakpointLocation {
/**
- * The source line of the breakpoint or logpoint.
+ * The line number in the source file.
+ */
+ private int lineNumberInSourceFile = Integer.MIN_VALUE;
+ /**
+ * The line number in the class file.
*/
private int lineNumber;
/**
@@ -110,4 +114,12 @@ public Types.BreakpointLocation[] availableBreakpointLocations() {
public void setAvailableBreakpointLocations(Types.BreakpointLocation[] availableBreakpointLocations) {
this.availableBreakpointLocations = availableBreakpointLocations;
}
+
+ public int lineNumberInSourceFile() {
+ return lineNumberInSourceFile == Integer.MIN_VALUE ? lineNumber : lineNumberInSourceFile;
+ }
+
+ public void setLineNumberInSourceFile(int lineNumberInSourceFile) {
+ this.lineNumberInSourceFile = lineNumberInSourceFile;
+ }
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java
index 9b972334d..c30aa8742 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java
@@ -310,4 +310,58 @@ public static String decodeURIComponent(String uri) {
return uri;
}
}
+
+ /**
+ * Find the mapped lines based on the given line number.
+ *
+ * The line mappings format is as follows:
+ * - [i]: key
+ * - [i+1]: value
+ */
+ public static int[] binarySearchMappedLines(int[] lineMappings, int targetLine) {
+ if (lineMappings == null || lineMappings.length == 0 || lineMappings.length % 2 != 0) {
+ return null;
+ }
+
+ final int MAX = lineMappings.length / 2 - 1;
+ int low = 0;
+ int high = MAX;
+ int found = -1;
+ while (low <= high) {
+ int mid = low + (high - low) / 2;
+ int actualMid = mid * 2;
+ if (lineMappings[actualMid] == targetLine) {
+ found = mid;
+ break;
+ }
+
+ if (lineMappings[actualMid] < targetLine) {
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ }
+
+ if (found == -1) {
+ return null;
+ }
+
+ // Find the duplicates in the sorted array
+ int left = found;
+ while ((left - 1) >= 0 && lineMappings[(left - 1) * 2] == targetLine) {
+ left--;
+ }
+
+ int right = found;
+ while ((right + 1) <= MAX && lineMappings[(right + 1) * 2] == targetLine) {
+ right++;
+ }
+
+ int[] values = new int[right - left + 1];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = lineMappings[(left + i) * 2 + 1];
+ }
+
+ return values;
+ }
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java
index b853e0469..8f595151f 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java
@@ -34,6 +34,7 @@
import com.microsoft.java.debug.core.adapter.handler.InlineValuesRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.LaunchRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.ProcessIdHandler;
+import com.microsoft.java.debug.core.adapter.handler.RefreshFramesHandler;
import com.microsoft.java.debug.core.adapter.handler.RefreshVariablesHandler;
import com.microsoft.java.debug.core.adapter.handler.RestartFrameHandler;
import com.microsoft.java.debug.core.adapter.handler.ScopesRequestHandler;
@@ -101,7 +102,7 @@ public CompletableFuture dispatchRequest(Messages.Request req
}
}
- private void initialize() {
+ protected void initialize() {
// Register request handlers.
// When there are multiple handlers registered for the same request, follow the rule "first register, first execute".
registerHandler(new InitializeRequestHandler());
@@ -133,21 +134,22 @@ private void initialize() {
registerHandlerForDebug(new SetFunctionBreakpointsRequestHandler());
registerHandlerForDebug(new BreakpointLocationsRequestHander());
registerHandlerForDebug(new StepInTargetsRequestHandler());
+ registerHandlerForDebug(new RefreshFramesHandler());
// NO_DEBUG mode only
registerHandlerForNoDebug(new DisconnectRequestWithoutDebuggingHandler());
registerHandlerForNoDebug(new ProcessIdHandler());
}
- private void registerHandlerForDebug(IDebugRequestHandler handler) {
+ protected void registerHandlerForDebug(IDebugRequestHandler handler) {
registerHandler(requestHandlersForDebug, handler);
}
- private void registerHandlerForNoDebug(IDebugRequestHandler handler) {
+ protected void registerHandlerForNoDebug(IDebugRequestHandler handler) {
registerHandler(requestHandlersForNoDebug, handler);
}
- private void registerHandler(IDebugRequestHandler handler) {
+ protected void registerHandler(IDebugRequestHandler handler) {
registerHandler(requestHandlersForDebug, handler);
registerHandler(requestHandlersForNoDebug, handler);
}
@@ -163,4 +165,5 @@ private void registerHandler(Map> requestHan
handlerList.add(handler);
}
}
+
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java
index f8ac01c3e..bbf215c67 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java
@@ -32,7 +32,7 @@
public class DebugAdapterContext implements IDebugAdapterContext {
private static final int MAX_CACHE_ITEMS = 10000;
private final StepFilters defaultFilters = new StepFilters();
- private Map sourceMappingCache = Collections.synchronizedMap(new LRUCache<>(MAX_CACHE_ITEMS));
+ private Map sourceMappingCache = Collections.synchronizedMap(new LRUCache<>(MAX_CACHE_ITEMS));
private IProviderContext providerContext;
private IProtocolServer server;
@@ -212,7 +212,7 @@ public void setVariableFormatter(IVariableFormatter variableFormatter) {
}
@Override
- public Map getSourceLookupCache() {
+ public Map getSourceLookupCache() {
return sourceMappingCache;
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java
index 9a38e8598..e65270eef 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java
@@ -85,7 +85,7 @@ public interface IDebugAdapterContext {
void setVariableFormatter(IVariableFormatter variableFormatter);
- Map getSourceLookupCache();
+ Map getSourceLookupCache();
void setDebuggeeEncoding(Charset encoding);
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterFactory.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterFactory.java
new file mode 100644
index 000000000..d392d3b48
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterFactory.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Microsoft Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Microsoft Corporation - initial API and implementation
+*******************************************************************************/
+
+package com.microsoft.java.debug.core.adapter;
+
+import com.microsoft.java.debug.core.protocol.IProtocolServer;
+
+@FunctionalInterface
+public interface IDebugAdapterFactory {
+ public IDebugAdapter create(IProtocolServer server);
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java
index ead10e33c..f33742852 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java
@@ -8,7 +8,6 @@
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
-
package com.microsoft.java.debug.core.adapter;
import java.util.List;
@@ -42,18 +41,31 @@ public interface ISourceLookUpProvider extends IProvider {
JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceBreakpoint[] sourceBreakpoints) throws DebugException;
/**
- * Given a fully qualified class name and source file path, search the associated disk source file.
- *
- * @param fullyQualifiedName
- * the fully qualified class name (e.g. com.microsoft.java.debug.core.adapter.ISourceLookUpProvider).
- * @param sourcePath
- * the qualified source file path (e.g. com\microsoft\java\debug\core\adapter\ISourceLookupProvider.java).
- * @return the associated source file uri.
+ * Deprecated, please use {@link #getSource(String, String)} instead.
*/
+ @Deprecated
String getSourceFileURI(String fullyQualifiedName, String sourcePath);
String getSourceContents(String uri);
+ /**
+ * Retrieves a {@link Source} object representing the source code associated with the given fully qualified class name and source file path.
+ * The implementation of this interface can determine a source is "local" or "remote".
+ * In case of "remote" a follow up "source" request will be issued by the client
+ *
+ * @param fullyQualifiedName
+ * the fully qualified class name,
+ * e.g., "com.microsoft.java.debug.core.adapter.ISourceLookUpProvider".
+ * @param sourcePath
+ * the qualified source file path,
+ * e.g., "com/microsoft/java/debug/core/adapter/ISourceLookupProvider.java".
+ * @return A {@link Source} object encapsulating the source file URI obtained from
+ * {@link #getSourceFileURI(String, String)} and the source type as {@link SourceType#LOCAL}.
+ */
+ default Source getSource(String fullyQualifiedName, String sourcePath) {
+ return new Source(getSourceFileURI(fullyQualifiedName, sourcePath), SourceType.LOCAL);
+ }
+
/**
* Returns the Java runtime that the specified project's build path used.
* @param projectName
@@ -76,6 +88,26 @@ default String getJavaRuntimeVersion(String projectName) {
*/
List findMethodInvocations(String uri, int line);
+ /**
+ * Return the line mappings from the original line to the decompiled line.
+ *
+ * @param uri The uri
+ * @return the line mappings from the original line to the decompiled line.
+ */
+ default int[] getOriginalLineMappings(String uri) {
+ return null;
+ }
+
+ /**
+ * Return the line mappings from the decompiled line to the original line.
+ *
+ * @param uri The uri
+ * @return the line mappings from the decompiled line to the original line.
+ */
+ default int[] getDecompiledLineMappings(String uri) {
+ return null;
+ }
+
public static class MethodInvocation {
public String expression;
public String methodName;
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java
index 0526293b9..9e503e0af 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java
@@ -52,6 +52,20 @@ public ProtocolServer(InputStream input, OutputStream output, IProviderContext c
debugAdapter = new DebugAdapter(this, context);
}
+ /**
+ * Constructs a protocol server instance based on the given input stream and output stream.
+ * @param input
+ * the input stream
+ * @param output
+ * the output stream
+ * @param debugAdapterFactory
+ * factory to create debug adapter that implements DAP communication
+ */
+ public ProtocolServer(InputStream input, OutputStream output, IDebugAdapterFactory debugAdapterFactory) {
+ super(input, output);
+ debugAdapter = debugAdapterFactory.create(this);
+ }
+
/**
* A while-loop to parse input data and send output data constantly.
*/
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Source.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Source.java
new file mode 100644
index 000000000..d00b4cb4c
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Source.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Microsoft Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Microsoft Corporation - initial API and implementation
+ *******************************************************************************/
+
+package com.microsoft.java.debug.core.adapter;
+
+public class Source {
+ public final String uri;
+ public final SourceType type;
+
+ public Source(String uri, SourceType type) {
+ this.uri = uri;
+ this.type = type;
+ }
+
+ public String getUri() {
+ return this.uri;
+ }
+
+ public SourceType getType() {
+ return this.type;
+ }
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/SourceType.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/SourceType.java
new file mode 100644
index 000000000..724bf3bda
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/SourceType.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Microsoft Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Microsoft Corporation - initial API and implementation
+ *******************************************************************************/
+package com.microsoft.java.debug.core.adapter;
+
+public enum SourceType {
+ REMOTE,
+ LOCAL
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ThreadCache.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ThreadCache.java
index ce1c17282..57d39154e 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ThreadCache.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ThreadCache.java
@@ -13,6 +13,7 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
@@ -32,6 +33,8 @@ protected boolean removeEldestEntry(java.util.Map.Entry eldest) {
}
});
private Map eventThreads = new ConcurrentHashMap<>();
+ private Map> decompiledClassesByThread = new HashMap<>();
+ private Map threadStoppedReasons = new HashMap<>();
public synchronized void resetThreads(List threads) {
allThreads.clear();
@@ -80,6 +83,13 @@ public void addEventThread(ThreadReference thread) {
eventThreads.put(thread.uniqueID(), thread);
}
+ public void addEventThread(ThreadReference thread, String reason) {
+ eventThreads.put(thread.uniqueID(), thread);
+ if (reason != null) {
+ threadStoppedReasons.put(thread.uniqueID(), reason);
+ }
+ }
+
public void removeEventThread(long threadId) {
eventThreads.remove(threadId);
}
@@ -113,4 +123,40 @@ public List visibleThreads(IDebugAdapterContext context) {
return visibleThreads;
}
+
+ public Set getDecompiledClassesByThread(long threadId) {
+ return decompiledClassesByThread.get(threadId);
+ }
+
+ public void setDecompiledClassesByThread(long threadId, Set decompiledClasses) {
+ if (decompiledClasses == null || decompiledClasses.isEmpty()) {
+ decompiledClassesByThread.remove(threadId);
+ return;
+ }
+
+ decompiledClassesByThread.put(threadId, decompiledClasses);
+ }
+
+ public String getThreadStoppedReason(long threadId) {
+ return threadStoppedReasons.get(threadId);
+ }
+
+ public void setThreadStoppedReason(long threadId, String reason) {
+ if (reason == null) {
+ threadStoppedReasons.remove(threadId);
+ return;
+ }
+
+ threadStoppedReasons.put(threadId, reason);
+ }
+
+ public void clearThreadStoppedState(long threadId) {
+ threadStoppedReasons.remove(threadId);
+ decompiledClassesByThread.remove(threadId);
+ }
+
+ public void clearAllThreadStoppedState() {
+ threadStoppedReasons.clear();
+ decompiledClassesByThread.clear();
+ }
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java
index 6805073dc..1c543bce4 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java
@@ -76,6 +76,7 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
if (context.isVmStopOnEntry()) {
DebugUtility.stopOnEntry(debugSession, context.getMainClass()).thenAccept(threadId -> {
context.getProtocolServer().sendEvent(new Events.StoppedEvent("entry", threadId));
+ context.getThreadCache().setThreadStoppedReason(threadId, "entry");
});
}
} else if (event instanceof VMDeathEvent) {
@@ -117,7 +118,7 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
JdiExceptionReference jdiException = new JdiExceptionReference(((ExceptionEvent) event).exception(),
((ExceptionEvent) event).catchLocation() == null);
context.getExceptionManager().setException(thread.uniqueID(), jdiException);
- context.getThreadCache().addEventThread(thread);
+ context.getThreadCache().addEventThread(thread, "exception");
context.getProtocolServer().sendEvent(new Events.StoppedEvent("exception", thread.uniqueID()));
debugEvent.shouldResume = false;
} else {
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java
index 27fdb1813..7370328b2 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java
@@ -95,7 +95,7 @@ public static synchronized Path generateClasspathJar(String[] classPaths) throws
// In jar manifest, the absolute path C:\a.jar should be converted to the url style file:///C:/a.jar
String classpathValue = String.join(" ", classpathUrls);
attributes.put(Attributes.Name.CLASS_PATH, classpathValue);
- String baseName = "cp_" + getMd5(classpathValue);
+ String baseName = "cp_" + getSha256(classpathValue);
cleanupTempFiles(baseName, ".jar");
Path tempfile = createTempFile(baseName, ".jar");
JarOutputStream jar = new JarOutputStream(new FileOutputStream(tempfile.toFile()), manifest);
@@ -127,7 +127,7 @@ public static synchronized Path generateArgfile(String vmArgs, String[] classPat
}
argfile = argfile.replace("\\", "\\\\");
- String baseName = "cp_" + getMd5(argfile);
+ String baseName = "cp_" + getSha256(argfile);
cleanupTempFiles(baseName, ".argfile");
Path tempfile = createTempFile(baseName, ".argfile");
Files.writeString(tempfile, argfile, encoding);
@@ -364,12 +364,15 @@ private static Path createTempFile(String baseName, String suffix) throws IOExce
}
}
- private static String getMd5(String input) {
+ private static String getSha256(String input) {
try {
- MessageDigest md = MessageDigest.getInstance("MD5");
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] messageDigest = md.digest(input.getBytes());
- BigInteger md5 = new BigInteger(1, messageDigest);
- return md5.toString(Character.MAX_RADIX);
+ // Use only first 16 bytes to keep filename shorter
+ byte[] truncated = new byte[16];
+ System.arraycopy(messageDigest, 0, truncated, 0, 16);
+ BigInteger hash = new BigInteger(1, truncated);
+ return hash.toString(Character.MAX_RADIX);
} catch (NoSuchAlgorithmException e) {
return Integer.toString(input.hashCode(), Character.MAX_RADIX);
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RefreshFramesHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RefreshFramesHandler.java
new file mode 100644
index 000000000..b91daaf80
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RefreshFramesHandler.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+* Copyright (c) 2023 Microsoft Corporation and others.
+* All rights reserved. This program and the accompanying materials
+* are made available under the terms of the Eclipse Public License v1.0
+* which accompanies this distribution, and is available at
+* http://www.eclipse.org/legal/epl-v10.html
+*
+* Contributors:
+* Microsoft Corporation - initial API and implementation
+*******************************************************************************/
+
+package com.microsoft.java.debug.core.adapter.handler;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+import com.microsoft.java.debug.core.AsyncJdwpUtils;
+import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
+import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
+import com.microsoft.java.debug.core.protocol.Events.StoppedEvent;
+import com.microsoft.java.debug.core.protocol.Messages.Response;
+import com.microsoft.java.debug.core.protocol.Requests.Arguments;
+import com.microsoft.java.debug.core.protocol.Requests.Command;
+import com.microsoft.java.debug.core.protocol.Requests.RefreshFramesArguments;
+import com.sun.jdi.ObjectCollectedException;
+import com.sun.jdi.ThreadReference;
+
+public class RefreshFramesHandler implements IDebugRequestHandler {
+
+ @Override
+ public List getTargetCommands() {
+ return Arrays.asList(Command.REFRESHFRAMES);
+ }
+
+ @Override
+ public CompletableFuture handle(Command command, Arguments arguments, Response response,
+ IDebugAdapterContext context) {
+ RefreshFramesArguments refreshArgs = (RefreshFramesArguments) arguments;
+ String[] affectedRootPaths = refreshArgs == null ? null : refreshArgs.affectedRootPaths;
+ List pausedThreads = getPausedThreads(context);
+ for (long threadId : pausedThreads) {
+ if (affectedRootPaths == null || affectedRootPaths.length == 0) {
+ refreshFrames(threadId, context);
+ continue;
+ }
+
+ Set decompiledClasses = context.getThreadCache().getDecompiledClassesByThread(threadId);
+ if (decompiledClasses == null || decompiledClasses.isEmpty()) {
+ continue;
+ }
+
+ if (anyInAffectedRootPaths(decompiledClasses, affectedRootPaths)) {
+ refreshFrames(threadId, context);
+ }
+ }
+
+ return CompletableFuture.completedFuture(response);
+ }
+
+ List getPausedThreads(IDebugAdapterContext context) {
+ List results = new ArrayList<>();
+ List> futures = new ArrayList<>();
+ List threads = context.getThreadCache().visibleThreads(context);
+ for (ThreadReference thread : threads) {
+ if (context.asyncJDWP()) {
+ futures.add(AsyncJdwpUtils.supplyAsync(() -> {
+ try {
+ if (thread.isSuspended()) {
+ return thread.uniqueID();
+ }
+ } catch (ObjectCollectedException ex) {
+ // Ignore it if the thread is garbage collected.
+ }
+
+ return -1L;
+ }));
+ } else {
+ try {
+ if (thread.isSuspended()) {
+ results.add(thread.uniqueID());
+ }
+ } catch (ObjectCollectedException ex) {
+ // Ignore it if the thread is garbage collected.
+ }
+ }
+ }
+
+ List awaitedResutls = AsyncJdwpUtils.await(futures);
+ for (Long threadId : awaitedResutls) {
+ if (threadId > 0) {
+ results.add(threadId);
+ }
+ }
+
+ return results;
+ }
+
+ /**
+ * See https://github.com/microsoft/vscode/issues/188606,
+ * VS Code doesn't provide a simple way to refetch the stack frames.
+ * We're going to resend a thread stopped event to trick the client
+ * into refetching the thread stack frames.
+ */
+ void refreshFrames(long threadId, IDebugAdapterContext context) {
+ StoppedEvent stoppedEvent = new StoppedEvent(context.getThreadCache().getThreadStoppedReason(threadId), threadId);
+ stoppedEvent.preserveFocusHint = true;
+ context.getProtocolServer().sendEvent(stoppedEvent);
+ }
+
+ boolean anyInAffectedRootPaths(Collection classes, String[] affectedRootPaths) {
+ if (affectedRootPaths == null || affectedRootPaths.length == 0) {
+ return true;
+ }
+
+ for (String classUri : classes) {
+ // decompiled class uri is like 'jdt://contents/rt.jar/java.io/PrintStream.class?=1.helloworld/%5C/usr%5C/lib%5C/jvm%5C/
+ // java-8-oracle%5C/jre%5C/lib%5C/rt.jar%3Cjava.io(PrintStream.class'.
+ if (classUri.startsWith("jdt://contents/")) {
+ String jarName = classUri.substring("jdt://contents/".length());
+ int sep = jarName.indexOf("/");
+ jarName = sep >= 0 ? jarName.substring(0, sep) : jarName;
+ for (String affectedRootPath : affectedRootPaths) {
+ if (affectedRootPath.endsWith("/" + jarName) || affectedRootPath.endsWith("\\" + jarName)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RestartFrameHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RestartFrameHandler.java
index 2023209f4..164909656 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RestartFrameHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RestartFrameHandler.java
@@ -122,6 +122,7 @@ private void stepInto(IDebugAdapterContext context, ThreadReference thread) {
// Have to send two events to keep the UI sync with the step in operations:
context.getProtocolServer().sendEvent(new Events.ContinuedEvent(thread.uniqueID()));
context.getProtocolServer().sendEvent(new Events.StoppedEvent("restartframe", thread.uniqueID()));
+ context.getThreadCache().setThreadStoppedReason(thread.uniqueID(), "restartframe");
});
request.enable();
thread.resume();
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java
index 7fe6c3fdd..09dafd1b0 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java
@@ -24,6 +24,8 @@
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugException;
+import com.microsoft.java.debug.core.DebugSettings;
+import com.microsoft.java.debug.core.DebugSettings.Switch;
import com.microsoft.java.debug.core.IBreakpoint;
import com.microsoft.java.debug.core.IDebugSession;
import com.microsoft.java.debug.core.IEvaluatableBreakpoint;
@@ -209,14 +211,14 @@ private void registerBreakpointHandler(IDebugAdapterContext context) {
if (resume) {
debugEvent.eventSet.resume();
} else {
- context.getThreadCache().addEventThread(bpThread);
+ context.getThreadCache().addEventThread(bpThread, breakpointName);
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
breakpointName, bpThread.uniqueID()));
}
});
});
} else {
- context.getThreadCache().addEventThread(bpThread);
+ context.getThreadCache().addEventThread(bpThread, breakpointName);
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
breakpointName, bpThread.uniqueID()));
}
@@ -296,7 +298,8 @@ public static boolean handleEvaluationResult(IDebugAdapterContext context, Threa
private Types.Breakpoint convertDebuggerBreakpointToClient(IBreakpoint breakpoint, IDebugAdapterContext context) {
int id = (int) breakpoint.getProperty("id");
boolean verified = breakpoint.getProperty("verified") != null && (boolean) breakpoint.getProperty("verified");
- int lineNumber = AdapterUtils.convertLineNumber(breakpoint.getLineNumber(), context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1());
+ int lineNumber = AdapterUtils.convertLineNumber(breakpoint.sourceLocation().lineNumberInSourceFile(),
+ context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1());
return new Types.Breakpoint(id, verified, lineNumber, "");
}
@@ -318,6 +321,19 @@ private IBreakpoint[] convertClientBreakpointsToDebugger(String sourceFile, Type
} catch (NumberFormatException e) {
hitCount = 0; // If hitCount is an illegal number, ignore hitCount condition.
}
+
+ if (DebugSettings.getCurrent().debugSupportOnDecompiledSource == Switch.ON) {
+ // Align the decompiled line with the original line.
+ int[] lineMappings = sourceProvider.getDecompiledLineMappings(sourceFile);
+ if (locations[i] != null && lineMappings != null) {
+ int lineNumberInSourceFile = locations[i].lineNumber();
+ int[] originalLines = AdapterUtils.binarySearchMappedLines(lineMappings, lineNumberInSourceFile);
+ if (originalLines != null && originalLines.length > 0) {
+ locations[i].setLineNumberInSourceFile(lineNumberInSourceFile);
+ locations[i].setLineNumber(originalLines[0]);
+ }
+ }
+ }
breakpoints[i] = context.getDebugSession().createBreakpoint(locations[i], hitCount, sourceBreakpoints[i].condition,
sourceBreakpoints[i].logMessage);
if (sourceProvider.supportsRealtimeBreakpointVerification() && StringUtils.isNotBlank(locations[i].className())) {
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java
index 6d3e8c0b1..373b1c31b 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java
@@ -151,13 +151,13 @@ private void registerWatchpointHandler(IDebugAdapterContext context) {
if (resume) {
debugEvent.eventSet.resume();
} else {
- context.getThreadCache().addEventThread(bpThread);
+ context.getThreadCache().addEventThread(bpThread, "data breakpoint");
context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID()));
}
});
});
} else {
- context.getThreadCache().addEventThread(bpThread);
+ context.getThreadCache().addEventThread(bpThread, "data breakpoint");
context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID()));
}
debugEvent.shouldResume = false;
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetExceptionBreakpointsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetExceptionBreakpointsRequestHandler.java
index 3a4e642c8..b51c5fe27 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetExceptionBreakpointsRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetExceptionBreakpointsRequestHandler.java
@@ -26,8 +26,8 @@
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
-import com.microsoft.java.debug.core.protocol.Requests.ClassFilters;
import com.microsoft.java.debug.core.protocol.Requests.Command;
+import com.microsoft.java.debug.core.protocol.Requests.ExceptionFilters;
import com.microsoft.java.debug.core.protocol.Requests.SetExceptionBreakpointsArguments;
import com.microsoft.java.debug.core.protocol.Types;
import com.sun.jdi.event.VMDeathEvent;
@@ -38,6 +38,7 @@ public class SetExceptionBreakpointsRequestHandler implements IDebugRequestHandl
private boolean isInitialized = false;
private boolean notifyCaught = false;
private boolean notifyUncaught = false;
+ private boolean asyncJDWP = false;
@Override
public List getTargetCommands() {
@@ -53,6 +54,7 @@ public synchronized CompletableFuture handle(Command command, Argument
if (!isInitialized) {
isInitialized = true;
debugSession = context.getDebugSession();
+ asyncJDWP = context.asyncJDWP();
DebugSettings.addDebugSettingChangeListener(this);
debugSession.getEventHub().events().subscribe(debugEvent -> {
if (debugEvent.event instanceof VMDeathEvent
@@ -77,10 +79,11 @@ public synchronized CompletableFuture handle(Command command, Argument
}
private void setExceptionBreakpoints(IDebugSession debugSession, boolean notifyCaught, boolean notifyUncaught) {
- ClassFilters exceptionFilters = DebugSettings.getCurrent().exceptionFilters;
+ ExceptionFilters exceptionFilters = DebugSettings.getCurrent().exceptionFilters;
+ String[] exceptionTypes = (exceptionFilters == null ? null : exceptionFilters.exceptionTypes);
String[] classFilters = (exceptionFilters == null ? null : exceptionFilters.allowClasses);
String[] classExclusionFilters = (exceptionFilters == null ? null : exceptionFilters.skipClasses);
- debugSession.setExceptionBreakpoints(notifyCaught, notifyUncaught, classFilters, classExclusionFilters);
+ debugSession.setExceptionBreakpoints(notifyCaught, notifyUncaught, exceptionTypes, classFilters, classExclusionFilters, this.asyncJDWP);
}
@Override
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java
index 59a948d37..96a0e395b 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java
@@ -165,7 +165,7 @@ private void registerMethodBreakpointHandler(IDebugAdapterContext context) {
if (resume) {
debugEvent.eventSet.resume();
} else {
- context.getThreadCache().addEventThread(bpThread);
+ context.getThreadCache().addEventThread(bpThread, "function breakpoint");
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
"function breakpoint", bpThread.uniqueID()));
}
@@ -173,7 +173,7 @@ private void registerMethodBreakpointHandler(IDebugAdapterContext context) {
});
} else {
- context.getThreadCache().addEventThread(bpThread);
+ context.getThreadCache().addEventThread(bpThread, "function breakpoint");
context.getProtocolServer()
.sendEvent(new Events.StoppedEvent("function breakpoint", bpThread.uniqueID()));
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java
index fbfce49ef..6f54bcedb 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java
@@ -15,8 +15,10 @@
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
@@ -24,15 +26,21 @@
import org.apache.commons.lang3.StringUtils;
+import com.google.gson.JsonObject;
import com.microsoft.java.debug.core.AsyncJdwpUtils;
+import com.microsoft.java.debug.core.DebugSettings;
+import com.microsoft.java.debug.core.DebugSettings.Switch;
import com.microsoft.java.debug.core.DebugUtility;
import com.microsoft.java.debug.core.IBreakpoint;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider;
+import com.microsoft.java.debug.core.adapter.Source;
+import com.microsoft.java.debug.core.adapter.SourceType;
import com.microsoft.java.debug.core.adapter.formatter.SimpleTypeFormatter;
import com.microsoft.java.debug.core.adapter.variables.StackFrameReference;
+import com.microsoft.java.debug.core.protocol.Events.TelemetryEvent;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
import com.microsoft.java.debug.core.protocol.Requests.Command;
@@ -52,6 +60,7 @@
import com.sun.jdi.request.BreakpointRequest;
public class StackTraceRequestHandler implements IDebugRequestHandler {
+ private ThreadLocal isDecompilerInvoked = new ThreadLocal<>();
@Override
public List getTargetCommands() {
@@ -60,22 +69,31 @@ public List getTargetCommands() {
@Override
public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
+ final long startAt = System.currentTimeMillis();
+ isDecompilerInvoked.set(false);
StackTraceArguments stacktraceArgs = (StackTraceArguments) arguments;
List result = new ArrayList<>();
if (stacktraceArgs.startFrame < 0 || stacktraceArgs.levels < 0) {
response.body = new Responses.StackTraceResponseBody(result, 0);
return CompletableFuture.completedFuture(response);
}
- ThreadReference thread = context.getThreadCache().getThread(stacktraceArgs.threadId);
+ long threadId = stacktraceArgs.threadId;
+ ThreadReference thread = context.getThreadCache().getThread(threadId);
if (thread == null) {
- thread = DebugUtility.getThread(context.getDebugSession(), stacktraceArgs.threadId);
+ thread = DebugUtility.getThread(context.getDebugSession(), threadId);
}
int totalFrames = 0;
if (thread != null) {
+ Set decompiledClasses = new LinkedHashSet<>();
try {
// Thread state has changed and then invalidate the stack frame cache.
if (stacktraceArgs.startFrame == 0) {
context.getStackFrameManager().clearStackFrames(thread);
+ } else {
+ Set existing = context.getThreadCache().getDecompiledClassesByThread(threadId);
+ if (existing != null) {
+ decompiledClasses.addAll(existing);
+ }
}
totalFrames = thread.frameCount();
@@ -95,6 +113,10 @@ public CompletableFuture handle(Command command, Arguments arguments,
Types.StackFrame lspFrame = convertDebuggerStackFrameToClient(jdiFrame, frameId, i == 0, context);
result.add(lspFrame);
frameReference.setSource(lspFrame.source);
+ int jdiLineNumber = AdapterUtils.convertLineNumber(jdiFrame.lineNumber, context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1());
+ if (jdiLineNumber != lspFrame.line && lspFrame.source != null && lspFrame.source.path != null) {
+ decompiledClasses.add(lspFrame.source.path);
+ }
}
} catch (IncompatibleThreadStateException | IndexOutOfBoundsException | URISyntaxException
| AbsentInformationException | ObjectCollectedException
@@ -103,14 +125,25 @@ public CompletableFuture handle(Command command, Arguments arguments,
// 1. the vscode has wrong parameter/wrong uri
// 2. the thread actually terminates
// TODO: should record a error log here.
+ } finally {
+ context.getThreadCache().setDecompiledClassesByThread(threadId, decompiledClasses);
}
}
response.body = new Responses.StackTraceResponseBody(result, totalFrames);
+ long duration = System.currentTimeMillis() - startAt;
+ JsonObject properties = new JsonObject();
+ properties.addProperty("command", "stackTrace");
+ properties.addProperty("duration", duration);
+ properties.addProperty("decompileSupport", DebugSettings.getCurrent().debugSupportOnDecompiledSource.toString());
+ if (isDecompilerInvoked.get() != null) {
+ properties.addProperty("isDecompilerInvoked", Boolean.toString(isDecompilerInvoked.get()));
+ }
+ context.getProtocolServer().sendEvent(new TelemetryEvent("dap", properties));
return CompletableFuture.completedFuture(response);
}
private static List resolveStackFrameInfos(StackFrame[] frames, boolean async)
- throws AbsentInformationException, IncompatibleThreadStateException {
+ throws AbsentInformationException, IncompatibleThreadStateException {
List jdiFrames = new ArrayList<>();
List> futures = new ArrayList<>();
for (StackFrame frame : frames) {
@@ -189,6 +222,17 @@ private Types.StackFrame convertDebuggerStackFrameToClient(StackFrameInfo jdiFra
// display "Unknown Source" in the Call Stack View.
clientSource = null;
}
+ // DAP specifies lineNumber to be set to 0 when unavailable
+ clientLineNumber = 0;
+ } else if (DebugSettings.getCurrent().debugSupportOnDecompiledSource == Switch.ON
+ && clientSource != null && clientSource.path != null) {
+ // Align the original line with the decompiled line.
+ int[] lineMappings = context.getProvider(ISourceLookUpProvider.class).getOriginalLineMappings(clientSource.path);
+ int[] renderLines = AdapterUtils.binarySearchMappedLines(lineMappings, clientLineNumber);
+ if (renderLines != null && renderLines.length > 0) {
+ clientLineNumber = renderLines[0];
+ isDecompilerInvoked.set(true);
+ }
}
int clientColumnNumber = context.isClientColumnsStartAt1() ? 1 : 0;
@@ -204,7 +248,7 @@ private Types.StackFrame convertDebuggerStackFrameToClient(StackFrameInfo jdiFra
});
if (match) {
clientColumnNumber = AdapterUtils.convertColumnNumber(breakpoint.getColumnNumber(),
- context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1());
+ context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1());
}
}
}
@@ -216,33 +260,44 @@ private Types.StackFrame convertDebuggerStackFrameToClient(StackFrameInfo jdiFra
/**
* Find the source mapping for the specified source file name.
*/
- public static Types.Source convertDebuggerSourceToClient(String fullyQualifiedName, String sourceName, String relativeSourcePath,
+ public static Types.Source convertDebuggerSourceToClient(String fullyQualifiedName, String sourceName,
+ String relativeSourcePath,
IDebugAdapterContext context) throws URISyntaxException {
+
// use a lru cache for better performance
- String uri = context.getSourceLookupCache().computeIfAbsent(fullyQualifiedName, key -> {
- String fromProvider = context.getProvider(ISourceLookUpProvider.class).getSourceFileURI(key, relativeSourcePath);
- // avoid return null which will cause the compute function executed again
- return StringUtils.isBlank(fromProvider) ? "" : fromProvider;
+ Source source = context.getSourceLookupCache().computeIfAbsent(fullyQualifiedName, key -> {
+ Source result = context.getProvider(ISourceLookUpProvider.class).getSource(key, relativeSourcePath);
+ if (result == null) {
+ return new Source("", SourceType.LOCAL);
+ }
+ return result;
});
+ Integer sourceReference = 0;
+ String uri = source.getUri();
+
+ if (source.getType().equals(SourceType.REMOTE)) {
+ sourceReference = context.createSourceReference(source.getUri());
+ }
+
if (!StringUtils.isBlank(uri)) {
// The Source.path could be a file system path or uri string.
if (uri.startsWith("file:")) {
String clientPath = AdapterUtils.convertPath(uri, context.isDebuggerPathsAreUri(), context.isClientPathsAreUri());
- return new Types.Source(sourceName, clientPath, 0);
+ return new Types.Source(sourceName, clientPath, sourceReference);
} else {
// If the debugger returns uri in the Source.path for the StackTrace response, VSCode client will try to find a TextDocumentContentProvider
// to render the contents.
// Language Support for Java by Red Hat extension has already registered a jdt TextDocumentContentProvider to parse the jdt-based uri.
// The jdt uri looks like 'jdt://contents/rt.jar/java.io/PrintStream.class?=1.helloworld/%5C/usr%5C/lib%5C/jvm%5C/java-8-oracle%5C/jre%5C/
// lib%5C/rt.jar%3Cjava.io(PrintStream.class'.
- return new Types.Source(sourceName, uri, 0);
+ return new Types.Source(sourceName, uri, sourceReference);
}
} else {
// If the source lookup engine cannot find the source file, then lookup it in the source directories specified by user.
String absoluteSourcepath = AdapterUtils.sourceLookup(context.getSourcePaths(), relativeSourcePath);
if (absoluteSourcepath != null) {
- return new Types.Source(sourceName, absoluteSourcepath, 0);
+ return new Types.Source(sourceName, absoluteSourcepath, sourceReference);
} else {
return null;
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java
index bd324a7fa..e8f782668 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java
@@ -268,7 +268,8 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
} else if (currentStackDepth == threadState.stackDepth) {
// If the ending step location is same as the original location where the step into operation is originated,
// do another step of the same kind.
- if (isSameLocation(currentStepLocation, threadState.stepLocation)) {
+ if (isSameLocation(currentStepLocation, threadState.stepLocation,
+ threadState.targetStepIn)) {
context.getStepResultManager().removeMethodResult(threadId);
threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread,
context.getStepFilters().allowClasses,
@@ -308,7 +309,7 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
if (threadState.eventSubscription != null) {
threadState.eventSubscription.dispose();
}
- context.getThreadCache().addEventThread(thread);
+ context.getThreadCache().addEventThread(thread, "step");
context.getProtocolServer().sendEvent(new Events.StoppedEvent("step", thread.uniqueID()));
debugEvent.shouldResume = false;
} else if (event instanceof MethodExitEvent) {
@@ -438,15 +439,19 @@ private boolean shouldDoExtraStepInto(int originalStackDepth, Location originalL
return true;
}
- private boolean isSameLocation(Location original, Location current) {
+ private boolean isSameLocation(Location current, Location original, MethodInvocation targetStepIn) {
if (original == null || current == null) {
return false;
}
Method originalMethod = original.method();
Method currentMethod = current.method();
+ // if the lines doesn't match, check if the current line is still behind the
+ // target if a target exist. This handles where the target is part of a
+ // expression which is wrapped.
return originalMethod.equals(currentMethod)
- && original.lineNumber() == current.lineNumber();
+ && (original.lineNumber() == current.lineNumber()
+ || (targetStepIn != null && targetStepIn.lineEnd >= current.lineNumber()));
}
/**
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ThreadsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ThreadsRequestHandler.java
index fceac73a5..6573f11da 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ThreadsRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ThreadsRequestHandler.java
@@ -138,6 +138,7 @@ private CompletableFuture pause(Requests.PauseArguments arguments, Res
context.getStepResultManager().removeAllMethodResults();
context.getDebugSession().suspend();
context.getProtocolServer().sendEvent(new Events.StoppedEvent("pause", arguments.threadId, true));
+ context.getThreadCache().setThreadStoppedReason(arguments.threadId, "pause");
}
return CompletableFuture.completedFuture(response);
}
@@ -158,12 +159,14 @@ private CompletableFuture resume(Requests.ContinueArguments arguments,
context.getStepResultManager().removeMethodResult(arguments.threadId);
context.getExceptionManager().removeException(arguments.threadId);
allThreadsContinued = false;
+ context.getThreadCache().clearThreadStoppedState(arguments.threadId);
DebugUtility.resumeThread(thread);
context.getStackFrameManager().clearStackFrames(thread);
checkThreadRunningAndRecycleIds(thread, context);
} else {
context.getStepResultManager().removeAllMethodResults();
context.getExceptionManager().removeAllExceptions();
+ context.getThreadCache().clearAllThreadStoppedState();
resumeVM(context);
context.getStackFrameManager().clearStackFrames();
context.getRecyclableIdPool().removeAllObjects();
@@ -175,6 +178,7 @@ private CompletableFuture resume(Requests.ContinueArguments arguments,
private CompletableFuture resumeAll(Requests.ThreadOperationArguments arguments, Response response, IDebugAdapterContext context) {
context.getStepResultManager().removeAllMethodResults();
context.getExceptionManager().removeAllExceptions();
+ context.getThreadCache().clearAllThreadStoppedState();
resumeVM(context);
context.getProtocolServer().sendEvent(new Events.ContinuedEvent(arguments.threadId, true));
context.getStackFrameManager().clearStackFrames();
@@ -190,6 +194,7 @@ private CompletableFuture resumeOthers(Requests.ThreadOperationArgumen
continue;
}
+ context.getThreadCache().clearThreadStoppedState(thread.uniqueID());
if (context.asyncJDWP()) {
futures.add(AsyncJdwpUtils.runAsync(() -> resumeThread(thread, context)));
} else {
@@ -203,6 +208,7 @@ private CompletableFuture resumeOthers(Requests.ThreadOperationArgumen
private CompletableFuture pauseAll(Requests.ThreadOperationArguments arguments, Response response, IDebugAdapterContext context) {
context.getDebugSession().suspend();
context.getProtocolServer().sendEvent(new Events.StoppedEvent("pause", arguments.threadId, true));
+ context.getThreadCache().setThreadStoppedReason(arguments.threadId, "pause");
return CompletableFuture.completedFuture(response);
}
@@ -300,6 +306,7 @@ private void pauseThread(ThreadReference thread, IDebugAdapterContext context) {
context.getStepResultManager().removeMethodResult(threadId);
thread.suspend();
context.getProtocolServer().sendEvent(new Events.StoppedEvent("pause", threadId));
+ context.getThreadCache().setThreadStoppedReason(threadId, "pause");
}
} catch (ObjectCollectedException ex) {
// the thread is garbage collected.
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Events.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Events.java
index 681ec543d..e692e9b8d 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Events.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Events.java
@@ -38,6 +38,11 @@ public static class StoppedEvent extends DebugEvent {
public String description;
public String text;
public boolean allThreadsStopped;
+ /**
+ * A value of true hints to the client that this event should not change the
+ * focus.
+ */
+ public Boolean preserveFocusHint;
/**
* Constructor.
@@ -246,6 +251,30 @@ public UserNotificationEvent(NotificationType notifyType, String message) {
}
}
+ public static class TelemetryEvent extends DebugEvent {
+ /**
+ * The telemetry event name.
+ */
+ public String name;
+
+ /**
+ * The properties is an object as below.
+ * {
+ * [key: string]: string | number;
+ * }
+ */
+ public Object properties;
+
+ /**
+ * Constructor.
+ */
+ public TelemetryEvent(String name, Object data) {
+ super("telemetry");
+ this.name = name;
+ this.properties = data;
+ }
+ }
+
public static enum InvalidatedAreas {
@SerializedName("all")
ALL,
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java
index 697eef93d..1129d230e 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java
@@ -67,6 +67,15 @@ public static class ClassFilters {
public String[] skipClasses = new String[0];
}
+ public static class ExceptionFilters extends ClassFilters {
+ /**
+ * Specifies that exceptions which are instances of refType will be reported.
+ * Note: this will include instances of sub-types. If null, all instances
+ * will be reported.
+ */
+ public String[] exceptionTypes = new String[0];
+ }
+
public static class StepFilters extends ClassFilters {
/**
* Deprecated - please use {@link ClassFilters#skipClasses } instead.
@@ -417,6 +426,15 @@ public static class BreakpointLocationsArguments extends Arguments {
public int endColumn;
}
+ public static class RefreshFramesArguments extends Arguments {
+ /**
+ * If provided, refresh the stack frames of the paused threads that previously
+ * requested decompiled sources for classes in the affected root paths.
+ * Otherwise, refresh all paused threads.
+ */
+ public String[] affectedRootPaths;
+ }
+
public static enum Command {
INITIALIZE("initialize", InitializeArguments.class),
LAUNCH("launch", LaunchArguments.class),
@@ -455,6 +473,7 @@ public static enum Command {
REFRESHVARIABLES("refreshVariables", RefreshVariablesArguments.class),
PROCESSID("processId", Arguments.class),
BREAKPOINTLOCATIONS("breakpointLocations", BreakpointLocationsArguments.class),
+ REFRESHFRAMES("refreshFrames", RefreshFramesArguments.class),
UNSUPPORTED("", Arguments.class);
private String command;
diff --git a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/BreakpointTest.java b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/BreakpointTest.java
new file mode 100644
index 000000000..5cc6fa3d7
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/BreakpointTest.java
@@ -0,0 +1,17 @@
+package com.microsoft.java.debug.core;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class BreakpointTest {
+ @Test
+ public void testToNoneGeneric() {
+ assertEquals("Ljava.util.List;", Breakpoint.toNoneGeneric("Ljava.util.List;"));
+ assertEquals("(Ljava/util/Map;)Ljava/util/Map;", Breakpoint.toNoneGeneric(
+ "(Ljava/util/Map;>;)Ljava/util/Map;>;"));
+ assertEquals("(Ljava/util/Map;)Ljava/util/Map;",
+ Breakpoint.toNoneGeneric(
+ "(Ljava/util/Map;Ljava/util/List;>;)Ljava/util/Map;Ljava/util/List;>;"));
+ }
+}
diff --git a/com.microsoft.java.debug.plugin/.classpath b/com.microsoft.java.debug.plugin/.classpath
index 3f86555d4..b2be945c8 100644
--- a/com.microsoft.java.debug.plugin/.classpath
+++ b/com.microsoft.java.debug.plugin/.classpath
@@ -1,16 +1,16 @@
-
+
-
+
-
+
diff --git a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF
index 3bc8a69fc..f2ceffb53 100644
--- a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF
+++ b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Java Debug Server Plugin
Bundle-SymbolicName: com.microsoft.java.debug.plugin;singleton:=true
-Bundle-Version: 0.44.0
-Bundle-RequiredExecutionEnvironment: JavaSE-11
+Bundle-Version: 0.53.2
+Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Bundle-Activator: com.microsoft.java.debug.plugin.internal.JavaDebuggerServerPlugin
Bundle-Vendor: Microsoft
@@ -21,8 +21,8 @@ Require-Bundle: org.eclipse.core.runtime,
org.apache.commons.lang3,
org.eclipse.lsp4j,
com.google.guava
-Bundle-ClassPath: lib/commons-io-2.11.0.jar,
+Bundle-ClassPath: lib/commons-io-2.19.0.jar,
.,
lib/rxjava-2.2.21.jar,
lib/reactive-streams-1.0.4.jar,
- lib/com.microsoft.java.debug.core-0.44.0.jar
+ lib/com.microsoft.java.debug.core-0.53.2.jar
diff --git a/com.microsoft.java.debug.plugin/pom.xml b/com.microsoft.java.debug.plugin/pom.xml
index 75466e4df..2e2c0e6a8 100644
--- a/com.microsoft.java.debug.plugin/pom.xml
+++ b/com.microsoft.java.debug.plugin/pom.xml
@@ -5,7 +5,7 @@
com.microsoft.java
java-debug-parent
- 0.44.0
+ 0.53.2
com.microsoft.java.debug.plugin
eclipse-plugin
@@ -51,12 +51,12 @@
commons-io
commons-io
- 2.11.0
+ 2.19.0
com.microsoft.java
com.microsoft.java.debug.core
- 0.44.0
+ 0.53.2
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BindingUtils.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BindingUtils.java
index b696be049..085bf0d4c 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BindingUtils.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BindingUtils.java
@@ -12,10 +12,12 @@
package com.microsoft.java.debug;
import org.eclipse.jdt.core.dom.IMethodBinding;
+import org.eclipse.jdt.internal.debug.core.breakpoints.LambdaLocationLocatorHelper;
/**
* Utility methods around working with JDT Bindings.
*/
+@SuppressWarnings("restriction")
public final class BindingUtils {
private BindingUtils() {
@@ -48,31 +50,14 @@ public static String getMethodName(IMethodBinding binding, boolean fromKey) {
}
/**
- * Returns the method signature of the method represented by the binding. Since
- * this implementation use the {@link IMethodBinding#getKey()} to extract the
- * signature from, the method name must be passed in.
+ * Returns the method signature of the method represented by the binding
+ * including the synthetic outer locals.
*
* @param binding the binding which the signature must be resolved for.
- * @param name the name of the method.
- * @return the signature or null if the signature could not be resolved from the
- * key.
+ * @return the signature or null if the signature could not be resolved.
*/
- public static String toSignature(IMethodBinding binding, String name) {
- // use key for now until JDT core provides a public API for this.
- // "Ljava/util/Arrays;.asList([TT;)Ljava/util/List;"
- // "([Ljava/lang/String;)V|Ljava/lang/InterruptedException;"
- String signatureString = binding.getKey();
- if (signatureString != null) {
- name = "." + name;
- int index = signatureString.indexOf(name);
- if (index > -1) {
- int exceptionIndex = signatureString.indexOf("|", signatureString.lastIndexOf(")"));
- if (exceptionIndex > -1) {
- return signatureString.substring(index + name.length(), exceptionIndex);
- }
- return signatureString.substring(index + name.length());
- }
- }
- return null;
+ public static String toSignature(IMethodBinding binding) {
+ return LambdaLocationLocatorHelper.toMethodSignature(binding);
}
+
}
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java
index 68f593aff..97581242e 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java
@@ -22,8 +22,8 @@ public class BreakpointLocationLocator
public BreakpointLocationLocator(CompilationUnit compilationUnit, int lineNumber,
boolean bindingsResolved,
- boolean bestMatch) {
- super(compilationUnit, lineNumber, bindingsResolved, bestMatch);
+ boolean bestMatch, int offset, int end) {
+ super(compilationUnit, lineNumber, bindingsResolved, bestMatch, offset, end);
}
@Override
@@ -46,7 +46,7 @@ public String getMethodSignature() {
if (this.methodBinding == null) {
return null;
}
- return BindingUtils.toSignature(this.methodBinding, getMethodName());
+ return BindingUtils.toSignature(this.methodBinding);
}
/**
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java
index afffd9741..a5456809a 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java
@@ -71,7 +71,7 @@ public String getMethodSignature() {
if (!this.found) {
return null;
}
- return BindingUtils.toSignature(this.lambdaMethodBinding, getMethodName());
+ return BindingUtils.toSignature(this.lambdaMethodBinding);
}
/**
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java
index 3551ab2e6..24c65785d 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java
@@ -195,7 +195,6 @@ private static String[] constructLaunchCommand(Map l
StringBuilder execString = new StringBuilder();
execString.append("\"" + javaHome + slash + "bin" + slash + javaExec + "\"");
- execString.append(" -Xdebug -Xnoagent -Djava.compiler=NONE");
execString.append(" -Xrunjdwp:transport=dt_socket,address=" + address + ",server=n,suspend=" + (suspend ? "y" : "n"));
if (javaOptions != null) {
execString.append(" " + javaOptions);
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/Compile.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/Compile.java
index 415456ce5..245281816 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/Compile.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/Compile.java
@@ -14,48 +14,96 @@
package com.microsoft.java.debug.plugin.internal;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
+import org.eclipse.core.resources.IBuildConfiguration;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.ls.core.internal.BuildWorkspaceStatus;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.ResourceUtils;
+import org.eclipse.jdt.ls.core.internal.handlers.BuildWorkspaceHandler;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
+import org.eclipse.lsp4j.TextDocumentIdentifier;
+import org.eclipse.lsp4j.extended.ProjectBuildParams;
import com.microsoft.java.debug.core.Configuration;
public class Compile {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
- public static BuildWorkspaceStatus compile(CompileParams params, IProgressMonitor monitor) {
- try {
- if (monitor.isCanceled()) {
- return BuildWorkspaceStatus.CANCELLED;
+ private static final int GRADLE_BS_COMPILATION_ERROR = 100;
+
+ public static Object compile(CompileParams params, IProgressMonitor monitor) {
+ if (params == null) {
+ throw new IllegalArgumentException("The compile parameters should not be null.");
+ }
+
+ IProject mainProject = JdtUtils.getMainProject(params.getProjectName(), params.getMainClass());
+ if (JdtUtils.isBspProject(mainProject) && !ProjectUtils.isGradleProject(mainProject)) {
+ // Just need to trigger a build for the target project, the Gradle build server will
+ // handle the build dependencies for us.
+ try {
+ ResourcesPlugin.getWorkspace().build(
+ new IBuildConfiguration[]{mainProject.getActiveBuildConfig()},
+ IncrementalProjectBuilder.INCREMENTAL_BUILD,
+ false /*buildReference*/,
+ monitor
+ );
+ } catch (CoreException e) {
+ if (e.getStatus().getCode() == IResourceStatus.BUILD_FAILED) {
+ return GRADLE_BS_COMPILATION_ERROR;
+ } else {
+ return BuildWorkspaceStatus.FAILED;
+ }
}
+ return BuildWorkspaceStatus.SUCCEED;
+ }
- long compileAt = System.currentTimeMillis();
- if (params != null && params.isFullBuild()) {
- ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.CLEAN_BUILD, monitor);
- ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, monitor);
- } else {
- ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
+ if (monitor.isCanceled()) {
+ return BuildWorkspaceStatus.CANCELLED;
+ }
+
+ ProjectBuildParams buildParams = new ProjectBuildParams();
+ List identifiers = new LinkedList<>();
+ buildParams.setFullBuild(params.isFullBuild);
+ for (IJavaProject javaProject : ProjectUtils.getJavaProjects()) {
+ if (ProjectsManager.getDefaultProject().equals(javaProject.getProject())) {
+ continue;
}
- logger.info("Time cost for ECJ: " + (System.currentTimeMillis() - compileAt) + "ms");
+ // we only build project which is not a BSP project, in case that the compile request is triggered by
+ // HCR with auto-build disabled, the build for BSP projects will be triggered by JavaHotCodeReplaceProvider.
+ if (!JdtUtils.isBspProject(javaProject.getProject())) {
+ identifiers.add(new TextDocumentIdentifier(javaProject.getProject().getLocationURI().toString()));
+ }
+ }
+ if (identifiers.size() == 0) {
+ return BuildWorkspaceStatus.SUCCEED;
+ }
+
+ buildParams.setIdentifiers(identifiers);
+ long compileAt = System.currentTimeMillis();
+ BuildWorkspaceHandler buildWorkspaceHandler = new BuildWorkspaceHandler(JavaLanguageServerPlugin.getProjectsManager());
+ BuildWorkspaceStatus status = buildWorkspaceHandler.buildProjects(buildParams, monitor);
+ logger.info("Time cost for ECJ: " + (System.currentTimeMillis() - compileAt) + "ms");
+ if (status == BuildWorkspaceStatus.FAILED || status == BuildWorkspaceStatus.CANCELLED) {
+ return status;
+ }
- IProject mainProject = params == null ? null : ProjectUtils.getProject(params.getProjectName());
+ try {
IResource currentResource = mainProject;
if (isUnmanagedFolder(mainProject) && StringUtils.isNotBlank(params.getMainClass())) {
IType mainType = ProjectUtils.getJavaProject(mainProject).findType(params.getMainClass());
@@ -99,17 +147,14 @@ public static BuildWorkspaceStatus compile(CompileParams params, IProgressMonito
}
}
- if (problemMarkers.isEmpty()) {
- return BuildWorkspaceStatus.SUCCEED;
+ if (!problemMarkers.isEmpty()) {
+ return BuildWorkspaceStatus.WITH_ERROR;
}
-
- return BuildWorkspaceStatus.WITH_ERROR;
} catch (CoreException e) {
- JavaLanguageServerPlugin.logException("Failed to build workspace.", e);
- return BuildWorkspaceStatus.FAILED;
- } catch (OperationCanceledException e) {
- return BuildWorkspaceStatus.CANCELLED;
+ JavaLanguageServerPlugin.log(e);
}
+
+ return BuildWorkspaceStatus.SUCCEED;
}
private static boolean isUnmanagedFolder(IProject project) {
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/CompletionProposalRequestor.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/CompletionProposalRequestor.java
index be7932992..431c283f5 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/CompletionProposalRequestor.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/CompletionProposalRequestor.java
@@ -28,6 +28,7 @@
import org.eclipse.jdt.core.CompletionContext;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.CompletionRequestor;
+import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
@@ -45,6 +46,7 @@
import org.eclipse.jdt.ls.core.internal.handlers.CompletionResponses;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
+import org.eclipse.lsp4j.CompletionItemLabelDetails;
import com.google.common.collect.ImmutableSet;
import com.microsoft.java.debug.core.Configuration;
@@ -70,6 +72,8 @@ public final class CompletionProposalRequestor extends CompletionRequestor {
CompletionItemKind.Text);
// @formatter:on
+ private static boolean isFilterFailed = false;
+
/**
* Constructor.
* @param typeRoot ITypeRoot
@@ -153,19 +157,40 @@ public List getCompletionItems() {
*/
public CompletionItem toCompletionItem(CompletionProposal proposal, int index) {
final CompletionItem $ = new CompletionItem();
- $.setKind(mapKind(proposal.getKind()));
+ $.setKind(mapKind(proposal.getKind(), proposal.getFlags()));
Map data = new HashMap<>();
data.put(CompletionResolveHandler.DATA_FIELD_REQUEST_ID, String.valueOf(response.getId()));
data.put(CompletionResolveHandler.DATA_FIELD_PROPOSAL_ID, String.valueOf(index));
$.setData(data);
this.descriptionProvider.updateDescription(proposal, $);
+ // Use fully qualified name as needed.
+ $.setInsertText(String.valueOf(proposal.getCompletion()));
adjustCompleteItem($);
$.setSortText(SortTextHelper.computeSortText(proposal));
return $;
}
private void adjustCompleteItem(CompletionItem item) {
- if (item.getKind() == CompletionItemKind.Function) {
+ CompletionItemKind itemKind = item.getKind();
+ if (itemKind == CompletionItemKind.Class || itemKind == CompletionItemKind.Interface
+ || itemKind == CompletionItemKind.Enum) {
+ // Display the package name in the label property.
+ CompletionItemLabelDetails labelDetails = item.getLabelDetails();
+ if (labelDetails != null && StringUtils.isNotBlank(labelDetails.getDescription())) {
+ item.setLabel(item.getLabel() + " - " + labelDetails.getDescription());
+ }
+ } else if (itemKind == CompletionItemKind.Function) {
+ // Merge the label details into the label property
+ // because the completion provider in DEBUG CONSOLE
+ // doesn't support the label details.
+ CompletionItemLabelDetails labelDetails = item.getLabelDetails();
+ if (labelDetails != null && StringUtils.isNotBlank(labelDetails.getDetail())) {
+ item.setLabel(item.getLabel() + labelDetails.getDetail());
+ }
+ if (labelDetails != null && StringUtils.isNotBlank(labelDetails.getDescription())) {
+ item.setLabel(item.getLabel() + " : " + labelDetails.getDescription());
+ }
+
String text = item.getInsertText();
if (StringUtils.isNotBlank(text) && !text.endsWith(")")) {
item.setInsertText(text + "()");
@@ -181,7 +206,7 @@ public void acceptContext(CompletionContext context) {
this.descriptionProvider = new CompletionProposalDescriptionProvider(context);
}
- private CompletionItemKind mapKind(final int kind) {
+ private CompletionItemKind mapKind(final int kind, final int flags) {
// When a new CompletionItemKind is added, don't forget to update
// SUPPORTED_KINDS
switch (kind) {
@@ -190,6 +215,11 @@ private CompletionItemKind mapKind(final int kind) {
return CompletionItemKind.Constructor;
case CompletionProposal.ANONYMOUS_CLASS_DECLARATION:
case CompletionProposal.TYPE_REF:
+ if (Flags.isInterface(flags)) {
+ return CompletionItemKind.Interface;
+ } else if (Flags.isEnum(flags)) {
+ return CompletionItemKind.Enum;
+ }
return CompletionItemKind.Class;
case CompletionProposal.FIELD_IMPORT:
case CompletionProposal.METHOD_IMPORT:
@@ -199,6 +229,9 @@ private CompletionItemKind mapKind(final int kind) {
return CompletionItemKind.Module;
case CompletionProposal.FIELD_REF:
case CompletionProposal.FIELD_REF_WITH_CASTED_RECEIVER:
+ if (Flags.isStatic(flags) && Flags.isFinal(flags)) {
+ return CompletionItemKind.Constant;
+ }
return CompletionItemKind.Field;
case CompletionProposal.KEYWORD:
return CompletionItemKind.Keyword;
@@ -290,7 +323,7 @@ private boolean isFiltered(CompletionProposal proposal) {
case CompletionProposal.JAVADOC_TYPE_REF:
case CompletionProposal.TYPE_REF: {
char[] declaringType = getDeclaringType(proposal);
- return declaringType != null && org.eclipse.jdt.ls.core.internal.contentassist.TypeFilter.isFiltered(declaringType);
+ return declaringType != null && isFiltered(declaringType);
}
default: // do nothing
}
@@ -301,6 +334,22 @@ private boolean isFiltered(CompletionProposal proposal) {
return false;
}
+ // Temp workaround for the completion error https://github.com/microsoft/java-debug/issues/534
+ private static boolean isFiltered(char[] fullTypeName) {
+ if (isFilterFailed) {
+ return false;
+ }
+
+ try {
+ return JavaLanguageServerPlugin.getInstance().getTypeFilter().filter(new String(fullTypeName));
+ } catch (NoSuchMethodError ex) {
+ isFilterFailed = true;
+ JavaLanguageServerPlugin.logException("isFiltered for the completion failed.", ex);
+ }
+
+ return false;
+ }
+
/**
* copied from
* org.eclipse.jdt.ui.text.java.CompletionProposalCollector.getDeclaringType(CompletionProposal)
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java
index 2bf905c89..3a2b2f3df 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java
@@ -34,16 +34,20 @@
import java.util.logging.Level;
import java.util.logging.Logger;
+import org.eclipse.core.resources.IBuildConfiguration;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
@@ -55,6 +59,7 @@
import org.eclipse.jdt.core.util.ISourceAttribute;
import org.eclipse.jdt.internal.core.util.Util;
import org.eclipse.jdt.ls.core.internal.JobHelpers;
+import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugException;
@@ -63,6 +68,7 @@
import com.microsoft.java.debug.core.IDebugSession;
import com.microsoft.java.debug.core.StackFrameUtility;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
+import com.microsoft.java.debug.core.adapter.Constants;
import com.microsoft.java.debug.core.adapter.ErrorCode;
import com.microsoft.java.debug.core.adapter.HotCodeReplaceEvent;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
@@ -104,6 +110,8 @@ public class JavaHotCodeReplaceProvider implements IHotCodeReplaceProvider, IRes
private List deltaClassNames = new ArrayList<>();
+ private String mainProjectName = "";
+
/**
* Visitor for resource deltas.
*/
@@ -269,6 +277,7 @@ public void initialize(IDebugAdapterContext context, Map options
}
this.context = context;
currentDebugSession = context.getDebugSession();
+ this.mainProjectName = ((String) options.get(Constants.PROJECT_NAME));
}
@Override
@@ -319,6 +328,7 @@ public void onClassRedefined(Consumer> consumer) {
@Override
public CompletableFuture> redefineClasses() {
+ triggerBuildForBspProject();
JobHelpers.waitForBuildJobs(10 * 1000);
return CompletableFuture.supplyAsync(() -> {
List classNames = new ArrayList<>();
@@ -737,4 +747,39 @@ private List getStackFrames(ThreadReference thread, boolean refresh)
}
});
}
+
+ /**
+ * Trigger build separately if the main project is a BSP project.
+ * This is because auto build for BSP project will not update the class files to disk.
+ */
+ private void triggerBuildForBspProject() {
+ // check if the workspace contains BSP project first. This is for performance consideration.
+ // Due to that getJavaProjectFromType() is a heavy operation.
+ if (!containsBspProjects()) {
+ return;
+ }
+
+ IProject mainProject = JdtUtils.getMainProject(this.mainProjectName, context.getMainClass());
+ if (mainProject != null && JdtUtils.isBspProject(mainProject)) {
+ try {
+ ResourcesPlugin.getWorkspace().build(
+ new IBuildConfiguration[]{mainProject.getActiveBuildConfig()},
+ IncrementalProjectBuilder.INCREMENTAL_BUILD,
+ false /*buildReference*/,
+ new NullProgressMonitor()
+ );
+ } catch (CoreException e) {
+ // ignore compilation errors
+ }
+ }
+ }
+
+ private boolean containsBspProjects() {
+ for (IJavaProject javaProject : ProjectUtils.getJavaProjects()) {
+ if (JdtUtils.isBspProject(javaProject.getProject())) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java
index c4d4fe318..5652b922e 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java
@@ -20,20 +20,27 @@
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
-import java.util.stream.Stream;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.debug.core.sourcelookup.ISourceContainer;
import org.eclipse.jdt.core.IBuffer;
@@ -50,21 +57,26 @@
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
-import org.eclipse.jdt.core.dom.LambdaExpression;
-import org.eclipse.jdt.core.manipulation.CoreASTProvider;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
+import org.eclipse.jdt.core.dom.LambdaExpression;
+import org.eclipse.jdt.core.manipulation.CoreASTProvider;
import org.eclipse.jdt.internal.core.JarPackageFragmentRoot;
import org.eclipse.jdt.launching.IVMInstall;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jdt.launching.LibraryLocation;
+import org.eclipse.jdt.ls.core.internal.DecompilerResult;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
+import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
+import org.eclipse.jdt.ls.core.internal.managers.ContentProviderManager;
import com.microsoft.java.debug.BindingUtils;
import com.microsoft.java.debug.BreakpointLocationLocator;
import com.microsoft.java.debug.LambdaExpressionLocator;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugException;
+import com.microsoft.java.debug.core.DebugSettings;
+import com.microsoft.java.debug.core.DebugSettings.Switch;
import com.microsoft.java.debug.core.JavaBreakpointLocation;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
import com.microsoft.java.debug.core.adapter.Constants;
@@ -77,6 +89,9 @@ public class JdtSourceLookUpProvider implements ISourceLookUpProvider {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
private static final String JDT_SCHEME = "jdt";
private static final String PATH_SEPARATOR = "/";
+ private static final Set IMPLICITLY_DECLARED_CLASSES = new HashSet<>(
+ Arrays.asList("org.eclipse.jdt.core.dom.UnnamedClass",
+ "org.eclipse.jdt.core.dom.ImplicitTypeDeclaration"));
private ISourceContainer[] sourceContainers = null;
private HashMap options = new HashMap();
@@ -98,7 +113,8 @@ public void initialize(IDebugAdapterContext context, Map props)
throw new IllegalArgumentException("argument is null");
}
options.putAll(props);
- // During initialization, trigger a background job to load the source containers to improve the perf.
+ // During initialization, trigger a background job to load the source containers
+ // to improve the perf.
new Thread(() -> {
getSourceContainers();
}).start();
@@ -140,15 +156,16 @@ public String[] getFullyQualifiedName(String uri, int[] lines, int[] columns) th
return Stream.of(locations).map(location -> {
if (location.className() != null && location.methodName() != null) {
return location.className()
- .concat("#").concat(location.methodName())
- .concat("#").concat(location.methodSignature());
+ .concat("#").concat(location.methodName())
+ .concat("#").concat(location.methodSignature());
}
return location.className();
}).toArray(String[]::new);
}
@Override
- public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceBreakpoint[] sourceBreakpoints) throws DebugException {
+ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceBreakpoint[] sourceBreakpoints)
+ throws DebugException {
if (sourceUri == null) {
throw new IllegalArgumentException("sourceUri is null");
}
@@ -159,9 +176,17 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
CompilationUnit astUnit = asCompilationUnit(sourceUri);
JavaBreakpointLocation[] sourceLocations = Stream.of(sourceBreakpoints)
- .map(sourceBreakpoint -> new JavaBreakpointLocation(sourceBreakpoint.line, sourceBreakpoint.column))
- .toArray(JavaBreakpointLocation[]::new);
+ .map(sourceBreakpoint -> new JavaBreakpointLocation(sourceBreakpoint.line, sourceBreakpoint.column))
+ .toArray(JavaBreakpointLocation[]::new);
if (astUnit != null) {
+ List> types = astUnit.types();
+ String unnamedClass = null;
+ // See https://github.com/eclipse-jdt/eclipse.jdt.core/pull/2220
+ // Given that the JDT plans to rename UnamedClass to ImplicitTypeDeclaration, we will check
+ // the class name of the ASTNode to prevent the potential breaking in the future.
+ if (types.size() == 1 && IMPLICITLY_DECLARED_CLASSES.contains(types.get(0).getClass().getName())) {
+ unnamedClass = inferPrimaryTypeName(sourceUri, astUnit);
+ }
Map resolvedLocations = new HashMap<>();
for (JavaBreakpointLocation sourceLocation : sourceLocations) {
int sourceLine = sourceLocation.lineNumber();
@@ -169,7 +194,7 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
if (sourceColumn > -1) {
// if we have a column, try to find the lambda expression at that column
LambdaExpressionLocator lambdaExpressionLocator = new LambdaExpressionLocator(astUnit,
- sourceLine, sourceColumn);
+ sourceLine, sourceColumn);
astUnit.accept(lambdaExpressionLocator);
if (lambdaExpressionLocator.isFound()) {
sourceLocation.setClassName(lambdaExpressionLocator.getFullyQualifiedTypeName());
@@ -200,8 +225,11 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
// mark it as "unverified".
// In future, we could consider supporting to update the breakpoint to a valid
// location.
+
+ // passing the offset to the constructor, it can recognize the multiline lambda
+ // expression well
BreakpointLocationLocator locator = new BreakpointLocationLocator(astUnit,
- sourceLine, true, true);
+ sourceLine, true, true, astUnit.getPosition(sourceLine, 0), 0);
astUnit.accept(locator);
// When the final valid line location is same as the original line, that
// represents it's a valid breakpoint.
@@ -209,7 +237,7 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
// be hit in current implementation.
if (sourceLine == locator.getLineLocation()
&& locator.getLocationType() == BreakpointLocationLocator.LOCATION_LINE) {
- sourceLocation.setClassName(locator.getFullyQualifiedTypeName());
+ sourceLocation.setClassName(StringUtils.isBlank(unnamedClass) ? locator.getFullyQualifiedTypeName() : unnamedClass);
if (resolvedLocations.containsKey(sourceLine)) {
sourceLocation.setAvailableBreakpointLocations(resolvedLocations.get(sourceLine));
} else {
@@ -218,7 +246,7 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
resolvedLocations.put(sourceLine, inlineLocations);
}
} else if (locator.getLocationType() == BreakpointLocationLocator.LOCATION_METHOD) {
- sourceLocation.setClassName(locator.getFullyQualifiedTypeName());
+ sourceLocation.setClassName(StringUtils.isBlank(unnamedClass) ? locator.getFullyQualifiedTypeName() : unnamedClass);
sourceLocation.setMethodName(locator.getMethodName());
sourceLocation.setMethodSignature(locator.getMethodSignature());
}
@@ -228,9 +256,31 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
return sourceLocations;
}
+ private String inferPrimaryTypeName(String uri, CompilationUnit astUnit) {
+ String fileName = "";
+ String filePath = AdapterUtils.toPath(uri);
+ if (filePath != null && Files.isRegularFile(Paths.get(filePath))) {
+ fileName = Paths.get(filePath).getFileName().toString();
+ } else if (astUnit.getTypeRoot() != null) {
+ fileName = astUnit.getTypeRoot().getElementName();
+ }
+
+ if (StringUtils.isNotBlank(fileName)) {
+ String[] extensions = JavaCore.getJavaLikeExtensions();
+ for (String extension : extensions) {
+ if (fileName.endsWith("." + extension)) {
+ return fileName.substring(0, fileName.length() - 1 - extension.length());
+ }
+ }
+ }
+
+ return fileName;
+ }
+
private BreakpointLocation[] getInlineBreakpointLocations(final CompilationUnit astUnit, int sourceLine) {
List locations = new ArrayList<>();
- // The starting position of each line is the default breakpoint location for that line.
+ // The starting position of each line is the default breakpoint location for
+ // that line.
locations.add(new BreakpointLocation(sourceLine, 0));
astUnit.accept(new ASTVisitor() {
@Override
@@ -276,26 +326,64 @@ private CompilationUnit asCompilationUnit(String uri) {
* setEnvironment(String [], String [], String [], boolean)
* and a unit name setUnitName(String).
*/
- parser.setEnvironment(new String[0], new String[0], null, true);
+ IFile resource = (IFile) JDTUtils.findResource(JDTUtils.toURI(uri),
+ ResourcesPlugin.getWorkspace().getRoot()::findFilesForLocationURI);
+ if (resource != null && JdtUtils.isJavaProject(resource.getProject())) {
+ parser.setProject(JavaCore.create(resource.getProject()));
+ } else {
+ parser.setEnvironment(new String[0], new String[0], null, true);
+ /**
+ * See the java doc for { @link ASTParser#setSource(char[]) },
+ * the user need specify the compiler options explicitly.
+ */
+ Map javaOptions = JavaCore.getOptions();
+ javaOptions.put(JavaCore.COMPILER_SOURCE, this.latestJavaVersion);
+ javaOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, this.latestJavaVersion);
+ javaOptions.put(JavaCore.COMPILER_COMPLIANCE, this.latestJavaVersion);
+ javaOptions.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, JavaCore.ENABLED);
+ parser.setCompilerOptions(javaOptions);
+ }
parser.setUnitName(Paths.get(filePath).getFileName().toString());
- /**
- * See the java doc for { @link ASTParser#setSource(char[]) },
- * the user need specify the compiler options explicitly.
- */
- Map javaOptions = JavaCore.getOptions();
- javaOptions.put(JavaCore.COMPILER_SOURCE, this.latestJavaVersion);
- javaOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, this.latestJavaVersion);
- javaOptions.put(JavaCore.COMPILER_COMPLIANCE, this.latestJavaVersion);
- javaOptions.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, JavaCore.ENABLED);
- parser.setCompilerOptions(javaOptions);
astUnit = (CompilationUnit) parser.createAST(null);
} else {
// For non-file uri (e.g. jdt://contents/rt.jar/java.io/PrintStream.class),
// leverage jdt to load the source contents.
- ITypeRoot typeRoot = resolveClassFile(uri);
- if (typeRoot != null) {
- parser.setSource(typeRoot);
- astUnit = (CompilationUnit) parser.createAST(null);
+ IClassFile typeRoot = resolveClassFile(uri);
+ try {
+ if (typeRoot != null && typeRoot.getSourceRange() != null) {
+ parser.setSource(typeRoot);
+ astUnit = (CompilationUnit) parser.createAST(null);
+ } else if (typeRoot != null && DebugSettings.getCurrent().debugSupportOnDecompiledSource == Switch.ON) {
+ ContentProviderManager contentProvider = JavaLanguageServerPlugin.getContentProviderManager();
+ try {
+ String contents = contentProvider.getSource(typeRoot, new NullProgressMonitor());
+ if (contents != null && !contents.isBlank()) {
+ IJavaProject javaProject = typeRoot.getJavaProject();
+ if (javaProject != null) {
+ parser.setProject(javaProject);
+ } else {
+ parser.setEnvironment(new String[0], new String[0], null, true);
+ /**
+ * See the java doc for { @link ASTParser#setSource(char[]) },
+ * the user need specify the compiler options explicitly.
+ */
+ Map javaOptions = JavaCore.getOptions();
+ javaOptions.put(JavaCore.COMPILER_SOURCE, this.latestJavaVersion);
+ javaOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, this.latestJavaVersion);
+ javaOptions.put(JavaCore.COMPILER_COMPLIANCE, this.latestJavaVersion);
+ javaOptions.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, JavaCore.ENABLED);
+ parser.setCompilerOptions(javaOptions);
+ }
+ parser.setUnitName(typeRoot.getElementName());
+ parser.setSource(contents.toCharArray());
+ astUnit = (CompilationUnit) parser.createAST(null);
+ }
+ } catch (Exception e) {
+ JavaLanguageServerPlugin.logException(e.getMessage(), e);
+ }
+ }
+ } catch (JavaModelException e) {
+ // ignore
}
}
return astUnit;
@@ -328,7 +416,8 @@ public String getJavaRuntimeVersion(String projectName) {
return resolveSystemLibraryVersion(project, vmInstall);
} catch (CoreException e) {
- logger.log(Level.SEVERE, "Failed to get Java runtime version for project '" + projectName + "': " + e.getMessage(), e);
+ logger.log(Level.SEVERE,
+ "Failed to get Java runtime version for project '" + projectName + "': " + e.getMessage(), e);
}
}
@@ -337,6 +426,7 @@ public String getJavaRuntimeVersion(String projectName) {
/**
* Get the project associated source containers.
+ *
* @return the initialized source container list
*/
public synchronized ISourceContainer[] getSourceContainers() {
@@ -365,7 +455,8 @@ private String getContents(IClassFile cf) {
source = buffer.getContents();
}
} catch (JavaModelException e) {
- logger.log(Level.SEVERE, String.format("Failed to parse the source contents of the class file: %s", e.toString()), e);
+ logger.log(Level.SEVERE,
+ String.format("Failed to parse the source contents of the class file: %s", e.toString()), e);
}
if (source == null) {
source = "";
@@ -380,7 +471,7 @@ private static String getFileURI(IClassFile classFile) {
try {
return new URI(JDT_SCHEME, "contents", PATH_SEPARATOR + jarName + PATH_SEPARATOR + packageName
+ PATH_SEPARATOR + classFile.getElementName(), classFile.getHandleIdentifier(), null)
- .toASCIIString();
+ .toASCIIString();
} catch (URISyntaxException e) {
return null;
}
@@ -417,8 +508,7 @@ private static IClassFile resolveClassFile(String uriString) {
private static String readFile(String filePath) {
StringBuilder builder = new StringBuilder();
- try (BufferedReader bufferReader =
- new BufferedReader(new InputStreamReader(new FileInputStream(filePath)))) {
+ try (BufferedReader bufferReader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath)))) {
final int BUFFER_SIZE = 4096;
char[] buffer = new char[BUFFER_SIZE];
while (true) {
@@ -434,7 +524,8 @@ private static String readFile(String filePath) {
return builder.toString();
}
- private static String resolveSystemLibraryVersion(IJavaProject project, IVMInstall vmInstall) throws JavaModelException {
+ private static String resolveSystemLibraryVersion(IJavaProject project, IVMInstall vmInstall)
+ throws JavaModelException {
LibraryLocation[] libraries = JavaRuntime.getLibraryLocations(vmInstall);
if (libraries != null && libraries.length > 0) {
IPackageFragmentRoot root = project.findPackageFragmentRoot(libraries[0].getSystemLibraryPath());
@@ -492,7 +583,7 @@ public List findMethodInvocations(String uri, int line) {
// Keep consistent with JDI since JDI uses binary class name
invocation.declaringTypeName = binding.getDeclaringClass().getBinaryName();
}
- invocation.methodGenericSignature = BindingUtils.toSignature(binding, BindingUtils.getMethodName(binding, true));
+ invocation.methodGenericSignature = BindingUtils.toSignature(binding);
invocation.methodSignature = Signature.getTypeErasure(invocation.methodGenericSignature);
int startOffset = astNode.getStartPosition();
if (astNode instanceof org.eclipse.jdt.core.dom.MethodInvocation) {
@@ -519,4 +610,58 @@ private boolean isSameURI(String uri1, String uri2) {
return false;
}
}
+
+ public int[] getOriginalLineMappings(String uri) {
+ IClassFile classFile = resolveClassFile(uri);
+ try {
+ if (classFile == null) {
+ return null;
+ }
+
+ IPackageFragmentRoot packageRoot = (IPackageFragmentRoot) classFile.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
+ if (packageRoot != null && packageRoot.getSourceAttachmentPath() != null) {
+ return null;
+ }
+
+ if (classFile.getSourceRange() == null) {
+ ContentProviderManager contentProvider = JavaLanguageServerPlugin.getContentProviderManager();
+ try {
+ DecompilerResult result = contentProvider.getSourceResult(classFile, new NullProgressMonitor());
+ if (result != null) {
+ return result.getOriginalLineMappings();
+ }
+ } catch (NoSuchMethodError e) {
+ // ignore it if old language server version is installed.
+ } catch (Exception e) {
+ JavaLanguageServerPlugin.logException(e.getMessage(), e);
+ }
+ }
+ } catch (JavaModelException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ public int[] getDecompiledLineMappings(String uri) {
+ IClassFile classFile = resolveClassFile(uri);
+ try {
+ if (classFile != null && classFile.getSourceRange() == null) {
+ ContentProviderManager contentProvider = JavaLanguageServerPlugin.getContentProviderManager();
+ try {
+ DecompilerResult result = contentProvider.getSourceResult(classFile, new NullProgressMonitor());
+ if (result != null) {
+ return result.getDecompiledLineMappings();
+ }
+ } catch (NoSuchMethodError e) {
+ // ignore it if old Java language server version is installed.
+ } catch (Exception e) {
+ JavaLanguageServerPlugin.logException(e.getMessage(), e);
+ }
+ }
+ } catch (JavaModelException e) {
+ // ignore
+ }
+
+ return null;
+ }
}
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java
index a4c06c6d4..66562ae74 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java
@@ -40,6 +40,8 @@
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jdt.launching.sourcelookup.containers.JavaProjectSourceContainer;
import org.eclipse.jdt.launching.sourcelookup.containers.PackageFragmentRootSourceContainer;
+import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
+import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.StackFrameUtility;
@@ -415,4 +417,36 @@ public static boolean isSameFile(IResource resource1, IResource resource2) {
return Objects.equals(resource1.getLocation(), resource2.getLocation());
}
+
+ /**
+ * Check if the project is managed by Gradle Build Server.
+ */
+ public static boolean isBspProject(IProject project) {
+ return project != null && ProjectUtils.isJavaProject(project)
+ && ProjectUtils.hasNature(project, "com.microsoft.gradle.bs.importer.GradleBuildServerProjectNature");
+ }
+
+ /**
+ * Get main project according to the main project name or main class name,
+ * or return null if the main project cannot be resolved.
+ */
+ public static IProject getMainProject(String mainProjectName, String mainClassName) {
+ IProject mainProject = null;
+ if (StringUtils.isNotBlank(mainProjectName)) {
+ mainProject = ProjectUtils.getProject(mainProjectName);
+ }
+
+ if (mainProject == null && StringUtils.isNotBlank(mainClassName)) {
+ try {
+ List javaProjects = ResolveClasspathsHandler.getJavaProjectFromType(mainClassName);
+ if (javaProjects.size() == 1) {
+ mainProject = javaProjects.get(0).getProject();
+ }
+ } catch (CoreException e) {
+ JavaLanguageServerPlugin.logException("Failed to resolve project from main class name.", e);
+ }
+ }
+
+ return mainProject;
+ }
}
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java
index a25fbc3f6..654cbb00b 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java
@@ -217,7 +217,7 @@ public boolean visit(ClassInstanceCreation node) {
private boolean shouldVisitNode(ASTNode node) {
int start = unit.getLineNumber(node.getStartPosition());
- int end = unit.getLineNumber(node.getStartPosition() + node.getLength());
+ int end = unit.getLineNumber(node.getStartPosition() + node.getLength() - 1);
if (line >= start && line <= end) {
return true;
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java
index f70d0c044..fc8189445 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java
@@ -35,6 +35,7 @@
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
+import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
@@ -43,6 +44,7 @@
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
+import org.eclipse.jdt.internal.core.SourceMethod;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.ResourceUtils;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
@@ -99,10 +101,19 @@ private List resolveMainClassCore(List