From 66feca936e8dd0bfbb7f6af3c3f4c4ce9833a95e Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Mon, 1 Feb 2021 13:19:52 +0800 Subject: [PATCH 1/7] Bump version to 0.30.0 (#361) --- com.microsoft.java.debug.core/pom.xml | 2 +- com.microsoft.java.debug.plugin/.classpath | 2 +- com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF | 4 ++-- com.microsoft.java.debug.plugin/pom.xml | 4 ++-- com.microsoft.java.debug.repository/category.xml | 2 +- com.microsoft.java.debug.repository/pom.xml | 2 +- pom.xml | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/com.microsoft.java.debug.core/pom.xml b/com.microsoft.java.debug.core/pom.xml index 24ec1b741..e29d730d1 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.29.0 + 0.30.0 com.microsoft.java.debug.core jar diff --git a/com.microsoft.java.debug.plugin/.classpath b/com.microsoft.java.debug.plugin/.classpath index 3291a02bb..08383fa97 100644 --- a/com.microsoft.java.debug.plugin/.classpath +++ b/com.microsoft.java.debug.plugin/.classpath @@ -6,6 +6,6 @@ - + diff --git a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF index 905946192..44401979f 100644 --- a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF +++ b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ 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.29.0 +Bundle-Version: 0.30.0 Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ActivationPolicy: lazy Bundle-Activator: com.microsoft.java.debug.plugin.internal.JavaDebuggerServerPlugin @@ -24,4 +24,4 @@ Bundle-ClassPath: lib/commons-io-2.5.jar, ., lib/rxjava-2.1.1.jar, lib/reactive-streams-1.0.0.jar, - lib/com.microsoft.java.debug.core-0.29.0.jar + lib/com.microsoft.java.debug.core-0.30.0.jar diff --git a/com.microsoft.java.debug.plugin/pom.xml b/com.microsoft.java.debug.plugin/pom.xml index 28901c18d..0150d3470 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.29.0 + 0.30.0 com.microsoft.java.debug.plugin eclipse-plugin @@ -45,7 +45,7 @@ com.microsoft.java com.microsoft.java.debug.core - 0.29.0 + 0.30.0 diff --git a/com.microsoft.java.debug.repository/category.xml b/com.microsoft.java.debug.repository/category.xml index e873f4578..c437e34f3 100644 --- a/com.microsoft.java.debug.repository/category.xml +++ b/com.microsoft.java.debug.repository/category.xml @@ -1,6 +1,6 @@ - + diff --git a/com.microsoft.java.debug.repository/pom.xml b/com.microsoft.java.debug.repository/pom.xml index ce56bf3a5..09cc52182 100644 --- a/com.microsoft.java.debug.repository/pom.xml +++ b/com.microsoft.java.debug.repository/pom.xml @@ -4,7 +4,7 @@ com.microsoft.java java-debug-parent - 0.29.0 + 0.30.0 com.microsoft.java.debug.repository eclipse-repository diff --git a/pom.xml b/pom.xml index 994191f7b..64805772b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ ${base.name} :: Parent The Java Debug Server is an implementation of Visual Studio Code (VSCode) Debug Protocol. It can be used in Visual Studio Code to debug Java programs. https://github.com/Microsoft/java-debug - 0.29.0 + 0.30.0 pom Java Debug Server for Visual Studio Code From 64d42435b9913ad39f49f8700e0c046abdc12dd5 Mon Sep 17 00:00:00 2001 From: Sheng Chen Date: Tue, 2 Mar 2021 03:58:11 -0800 Subject: [PATCH 2/7] Enable GitHub Actions (#363) Signed-off-by: Sheng Chen --- .github/workflows/build.yml | 91 +++++++++++++++++++++++++++++++++++++ .travis.yml | 13 ------ 2 files changed, 91 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..afa1e2185 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,91 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + linux: + name: Linux + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v2 + + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: '11' + + - 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 + + 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 + + - uses: actions/checkout@v2 + + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: '11' + + - 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: Verify + run: ./mvnw.cmd clean verify + + - 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 11 + uses: actions/setup-java@v1 + with: + java-version: '11' + + - 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 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f331372be..000000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: java - -os: - - linux - - osx - -script: - - ./mvnw clean verify - - ./mvnw checkstyle:check - -cache: - directories: - - $HOME/.m2 From e55f86413b05446f1cb99c30ce2b04c4363e9525 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Tue, 23 Mar 2021 12:20:05 +0800 Subject: [PATCH 3/7] Remove the usage of JDK internal API to avoid breaking JDK 16 (#364) --- com.microsoft.java.debug.core/pom.xml | 2 +- .../core/adapter/handler/LaunchUtils.java | 19 ++++++------------- com.microsoft.java.debug.plugin/.classpath | 2 +- .../META-INF/MANIFEST.MF | 4 ++-- com.microsoft.java.debug.plugin/pom.xml | 4 ++-- .../category.xml | 2 +- com.microsoft.java.debug.repository/pom.xml | 2 +- pom.xml | 2 +- 8 files changed, 15 insertions(+), 22 deletions(-) diff --git a/com.microsoft.java.debug.core/pom.xml b/com.microsoft.java.debug.core/pom.xml index e29d730d1..7c20a8aae 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.30.0 + 0.30.1 com.microsoft.java.debug.core jar 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 be7f95126..17a4f7359 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 @@ -16,15 +16,14 @@ import java.io.IOException; import java.math.BigInteger; import java.nio.file.Files; -import java.nio.file.InvalidPathException; import java.nio.file.Path; -import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; import java.util.jar.Attributes; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; @@ -33,8 +32,6 @@ import org.apache.commons.lang3.ArrayUtils; -import sun.security.action.GetPropertyAction; - public class LaunchUtils { private static Set tempFilesInUse = new HashSet<>(); @@ -109,16 +106,12 @@ public static void releaseTempLaunchFile(Path tempFile) { private static synchronized Path getTmpDir() throws IOException { if (tmpdir == null) { + Path tmpfile = Files.createTempFile("", UUID.randomUUID().toString()); + tmpdir = tmpfile.getParent(); try { - tmpdir = Paths.get(java.security.AccessController.doPrivileged(new GetPropertyAction("java.io.tmpdir"))); - } catch (NullPointerException | InvalidPathException e) { - Path tmpfile = Files.createTempFile("", ".tmp"); - tmpdir = tmpfile.getParent(); - try { - Files.deleteIfExists(tmpfile); - } catch (Exception ex) { - // do nothing - } + Files.deleteIfExists(tmpfile); + } catch (Exception ex) { + // do nothing } } diff --git a/com.microsoft.java.debug.plugin/.classpath b/com.microsoft.java.debug.plugin/.classpath index 08383fa97..e9e2f6c74 100644 --- a/com.microsoft.java.debug.plugin/.classpath +++ b/com.microsoft.java.debug.plugin/.classpath @@ -6,6 +6,6 @@ - + diff --git a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF index 44401979f..95fc9aeeb 100644 --- a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF +++ b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ 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.30.0 +Bundle-Version: 0.30.1 Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ActivationPolicy: lazy Bundle-Activator: com.microsoft.java.debug.plugin.internal.JavaDebuggerServerPlugin @@ -24,4 +24,4 @@ Bundle-ClassPath: lib/commons-io-2.5.jar, ., lib/rxjava-2.1.1.jar, lib/reactive-streams-1.0.0.jar, - lib/com.microsoft.java.debug.core-0.30.0.jar + lib/com.microsoft.java.debug.core-0.30.1.jar diff --git a/com.microsoft.java.debug.plugin/pom.xml b/com.microsoft.java.debug.plugin/pom.xml index 0150d3470..2a1985e15 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.30.0 + 0.30.1 com.microsoft.java.debug.plugin eclipse-plugin @@ -45,7 +45,7 @@ com.microsoft.java com.microsoft.java.debug.core - 0.30.0 + 0.30.1 diff --git a/com.microsoft.java.debug.repository/category.xml b/com.microsoft.java.debug.repository/category.xml index c437e34f3..d351c135e 100644 --- a/com.microsoft.java.debug.repository/category.xml +++ b/com.microsoft.java.debug.repository/category.xml @@ -1,6 +1,6 @@ - + diff --git a/com.microsoft.java.debug.repository/pom.xml b/com.microsoft.java.debug.repository/pom.xml index 09cc52182..09bd08f1b 100644 --- a/com.microsoft.java.debug.repository/pom.xml +++ b/com.microsoft.java.debug.repository/pom.xml @@ -4,7 +4,7 @@ com.microsoft.java java-debug-parent - 0.30.0 + 0.30.1 com.microsoft.java.debug.repository eclipse-repository diff --git a/pom.xml b/pom.xml index 64805772b..fe8385811 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ ${base.name} :: Parent The Java Debug Server is an implementation of Visual Studio Code (VSCode) Debug Protocol. It can be used in Visual Studio Code to debug Java programs. https://github.com/Microsoft/java-debug - 0.30.0 + 0.30.1 pom Java Debug Server for Visual Studio Code From e521ed1d53faee58fe3a868a3de1fbc21f58b5a0 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Wed, 21 Apr 2021 12:35:44 +0800 Subject: [PATCH 4/7] Leverage AST and Java Runtime to resolve inline values (#368) * Leverage AST and Java Runtime to resolve inline values --- .../java/debug/core/adapter/DebugAdapter.java | 2 + .../handler/InlineValuesRequestHandler.java | 250 +++++++ .../java/debug/core/protocol/Requests.java | 29 + .../java/debug/core/protocol/Responses.java | 9 + .../java/debug/core/protocol/Types.java | 8 + .../META-INF/MANIFEST.MF | 1 + com.microsoft.java.debug.plugin/plugin.xml | 1 + .../plugin/internal/InlineValueHandler.java | 644 ++++++++++++++++++ .../JavaDebugDelegateCommandHandler.java | 4 + 9 files changed, 948 insertions(+) create mode 100644 com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InlineValuesRequestHandler.java create mode 100644 com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/InlineValueHandler.java 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 798200e97..db2b28adf 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 @@ -30,6 +30,7 @@ import com.microsoft.java.debug.core.adapter.handler.ExceptionInfoRequestHandler; import com.microsoft.java.debug.core.adapter.handler.HotCodeReplaceHandler; import com.microsoft.java.debug.core.adapter.handler.InitializeRequestHandler; +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.RestartFrameHandler; import com.microsoft.java.debug.core.adapter.handler.ScopesRequestHandler; @@ -121,6 +122,7 @@ private void initialize() { registerHandlerForDebug(new ExceptionInfoRequestHandler()); registerHandlerForDebug(new DataBreakpointInfoRequestHandler()); registerHandlerForDebug(new SetDataBreakpointsRequestHandler()); + registerHandlerForDebug(new InlineValuesRequestHandler()); // NO_DEBUG mode only registerHandlerForNoDebug(new DisconnectRequestWithoutDebuggingHandler()); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InlineValuesRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InlineValuesRequestHandler.java new file mode 100644 index 000000000..e9697f899 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InlineValuesRequestHandler.java @@ -0,0 +1,250 @@ +/******************************************************************************* +* Copyright (c) 2021 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.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugSettings; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.adapter.IStackFrameManager; +import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructure; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructureManager; +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.adapter.variables.Variable; +import com.microsoft.java.debug.core.adapter.variables.VariableDetailUtils; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types; +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.InlineVariable; +import com.microsoft.java.debug.core.protocol.Requests.InlineValuesArguments; +import com.sun.jdi.ArrayReference; +import com.sun.jdi.Field; +import com.sun.jdi.IntegerValue; +import com.sun.jdi.Method; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.StackFrame; +import com.sun.jdi.Value; + +import org.apache.commons.lang3.math.NumberUtils; + +public class InlineValuesRequestHandler implements IDebugRequestHandler { + protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.INLINEVALUES); + } + + /** + * This request only resolves the values for those non-local variables, such as + * field variables and captured variables from outer scope. Because the values + * of local variables in current stackframe are usually expanded by Variables View + * by default, inline values can reuse these values directly. However, for field + * variables and variables captured from external scopes, they are hidden as properties + * of 'this' variable and require additional evaluation to get their values. + */ + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + InlineValuesArguments inlineValuesArgs = (InlineValuesArguments) arguments; + int variableCount = inlineValuesArgs == null || inlineValuesArgs.variables == null ? 0 : inlineValuesArgs.variables.length; + InlineVariable[] inlineVariables = inlineValuesArgs.variables; + StackFrameReference stackFrameReference = (StackFrameReference) context.getRecyclableIdPool().getObjectById(inlineValuesArgs.frameId); + if (stackFrameReference == null) { + logger.log(Level.SEVERE, String.format("InlineValues failed: invalid stackframe id %d.", inlineValuesArgs.frameId)); + response.body = new Responses.InlineValuesResponse(null); + return CompletableFuture.completedFuture(response); + } + + IStackFrameManager stackFrameManager = context.getStackFrameManager(); + StackFrame frame = stackFrameManager.getStackFrame(stackFrameReference); + if (frame == null) { + logger.log(Level.SEVERE, String.format("InlineValues failed: stale stackframe id %d.", inlineValuesArgs.frameId)); + response.body = new Responses.InlineValuesResponse(null); + return CompletableFuture.completedFuture(response); + } + + Variable[] values = new Variable[variableCount]; + try { + if (isLambdaFrame(frame)) { + // Lambda expression stores the captured variables from 'outer' scope in a synthetic stackframe below the lambda frame. + StackFrame syntheticLambdaFrame = stackFrameReference.getThread().frame(stackFrameReference.getDepth() + 1); + resolveValuesFromThisVariable(syntheticLambdaFrame.thisObject(), inlineVariables, values, true); + } + + resolveValuesFromThisVariable(frame.thisObject(), inlineVariables, values, false); + } catch (Exception ex) { + // do nothig + } + + Types.Variable[] result = new Types.Variable[variableCount]; + IVariableFormatter variableFormatter = context.getVariableFormatter(); + Map formatterOptions = variableFormatter.getDefaultOptions(); + Map calculatedValues = new HashMap<>(); + IEvaluationProvider evaluationEngine = context.getProvider(IEvaluationProvider.class); + for (int i = 0; i < variableCount; i++) { + if (values[i] == null) { + continue; + } + + if (calculatedValues.containsKey(inlineVariables[i])) { + result[i] = calculatedValues.get(inlineVariables[i]); + continue; + } + + Value value = values[i].value; + String name = values[i].name; + int indexedVariables = -1; + Value sizeValue = null; + if (value instanceof ArrayReference) { + indexedVariables = ((ArrayReference) value).length(); + } else if (value instanceof ObjectReference && DebugSettings.getCurrent().showLogicalStructure && evaluationEngine != null) { + try { + JavaLogicalStructure structure = JavaLogicalStructureManager.getLogicalStructure((ObjectReference) value); + if (structure != null && structure.getSizeExpression() != null) { + sizeValue = structure.getSize((ObjectReference) value, frame.thread(), evaluationEngine); + if (sizeValue != null && sizeValue instanceof IntegerValue) { + indexedVariables = ((IntegerValue) sizeValue).value(); + } + } + } catch (CancellationException | IllegalArgumentException | InterruptedException | ExecutionException | UnsupportedOperationException e) { + logger.log(Level.INFO, + String.format("Failed to get the logical size for the type %s.", value.type().name()), e); + } + } + + Types.Variable formattedVariable = new Types.Variable(name, variableFormatter.valueToString(value, formatterOptions)); + formattedVariable.indexedVariables = Math.max(indexedVariables, 0); + String detailsValue = null; + if (sizeValue != null) { + detailsValue = "size=" + variableFormatter.valueToString(sizeValue, formatterOptions); + } else if (DebugSettings.getCurrent().showToString) { + detailsValue = VariableDetailUtils.formatDetailsValue(value, frame.thread(), variableFormatter, formatterOptions, evaluationEngine); + } + + if (detailsValue != null) { + formattedVariable.value = formattedVariable.value + " " + detailsValue; + } + + result[i] = formattedVariable; + calculatedValues.put(inlineVariables[i], formattedVariable); + } + + response.body = new Responses.InlineValuesResponse(result); + return CompletableFuture.completedFuture(response); + } + + private static boolean isCapturedLocalVariable(String fieldName, String variableName) { + String capturedVariableName = "val$" + variableName; + return Objects.equals(fieldName, capturedVariableName) + || (fieldName.startsWith(capturedVariableName + "$") && NumberUtils.isDigits(fieldName.substring(capturedVariableName.length() + 1))); + } + + private static boolean isCapturedThisVariable(String fieldName) { + if (fieldName.startsWith("this$")) { + String suffix = fieldName.substring(5).replaceAll("\\$+$", ""); + return NumberUtils.isDigits(suffix); + } + + return false; + } + + private static boolean isLambdaFrame(StackFrame frame) { + Method method = frame.location().method(); + return method.isSynthetic() && method.name().startsWith("lambda$"); + } + + private void resolveValuesFromThisVariable(ObjectReference thisObj, InlineVariable[] unresolvedVariables, Variable[] result, + boolean isSyntheticLambdaFrame) { + if (thisObj == null) { + return; + } + + int unresolved = 0; + for (Variable item : result) { + if (item == null) { + unresolved++; + } + } + + try { + ReferenceType type = thisObj.referenceType(); + String typeName = type.name(); + ObjectReference enclosingInstance = null; + for (Field field : type.allFields()) { + String fieldName = field.name(); + boolean isSyntheticField = field.isSynthetic(); + Value fieldValue = null; + for (int i = 0; i < unresolvedVariables.length; i++) { + if (result[i] != null) { + continue; + } + + InlineVariable inlineVariable = unresolvedVariables[i]; + boolean isInlineFieldVariable = (inlineVariable.declaringClass != null); + boolean isMatch = false; + if (isSyntheticLambdaFrame) { + isMatch = !isInlineFieldVariable && Objects.equals(fieldName, inlineVariable.expression); + } else { + boolean isMatchedField = isInlineFieldVariable + && Objects.equals(fieldName, inlineVariable.expression) + && Objects.equals(typeName, inlineVariable.declaringClass); + boolean isMatchedCapturedVariable = !isInlineFieldVariable + && isSyntheticField + && isCapturedLocalVariable(fieldName, inlineVariable.expression); + isMatch = isMatchedField || isMatchedCapturedVariable; + + if (!isMatch && isSyntheticField && enclosingInstance == null && isCapturedThisVariable(fieldName)) { + Value value = thisObj.getValue(field); + if (value instanceof ObjectReference) { + enclosingInstance = (ObjectReference) value; + break; + } + } + } + + if (isMatch) { + fieldValue = fieldValue == null ? thisObj.getValue(field) : fieldValue; + result[i] = new Variable(inlineVariable.expression, fieldValue); + unresolved--; + } + } + + if (unresolved <= 0) { + break; + } + } + + if (unresolved > 0 && enclosingInstance != null) { + resolveValuesFromThisVariable(enclosingInstance, unresolvedVariables, result, isSyntheticLambdaFrame); + } + } catch (Exception ex) { + // do nothing + } + } +} 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 f62a241a1..2afb02bd8 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 @@ -13,6 +13,7 @@ import java.util.Arrays; import java.util.Map; +import java.util.Objects; import com.google.gson.annotations.SerializedName; import com.microsoft.java.debug.core.protocol.Types.DataBreakpoint; @@ -340,6 +341,33 @@ public static class SetDataBreakpointsArguments extends Arguments { public DataBreakpoint[] breakpoints; } + public static class InlineValuesArguments extends Arguments { + public int frameId; + public InlineVariable[] variables; + } + + public static class InlineVariable { + public String expression; + public String declaringClass; + + @Override + public int hashCode() { + return Objects.hash(declaringClass, expression); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof InlineVariable)) { + return false; + } + InlineVariable other = (InlineVariable) obj; + return Objects.equals(declaringClass, other.declaringClass) && Objects.equals(expression, other.expression); + } + } + public static enum Command { INITIALIZE("initialize", InitializeArguments.class), LAUNCH("launch", LaunchArguments.class), @@ -372,6 +400,7 @@ public static enum Command { CONTINUEOTHERS("continueOthers", ThreadOperationArguments.class), PAUSEALL("pauseAll", ThreadOperationArguments.class), PAUSEOTHERS("pauseOthers", ThreadOperationArguments.class), + INLINEVALUES("inlineValues", InlineValuesArguments.class), UNSUPPORTED("", Arguments.class); private String command; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java index 1b4bc7b4a..cc349b6e7 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java @@ -16,6 +16,7 @@ import com.microsoft.java.debug.core.protocol.Types.DataBreakpointAccessType; import com.microsoft.java.debug.core.protocol.Types.ExceptionBreakMode; import com.microsoft.java.debug.core.protocol.Types.ExceptionDetails; +import com.microsoft.java.debug.core.protocol.Types.Variable; /** * The response content types defined by VSCode Debug Protocol. @@ -316,4 +317,12 @@ public RedefineClassesResponse(String[] changedClasses, String errorMessage) { this.errorMessage = errorMessage; } } + + public static class InlineValuesResponse extends ResponseBody { + public Types.Variable[] variables; + + public InlineValuesResponse(Variable[] variables) { + this.variables = variables; + } + } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java index 3ce41869c..0b9a0494a 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java @@ -101,6 +101,14 @@ public Variable(String name, String val, String type, int rf, String evaluateNam this.variablesReference = rf; this.evaluateName = evaluateName; } + + /** + * Constructor. + */ + public Variable(String name, String value) { + this.name = name; + this.value = value; + } } public static class Thread { diff --git a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF index 95fc9aeeb..40f78351f 100644 --- a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF +++ b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF @@ -14,6 +14,7 @@ Require-Bundle: org.eclipse.core.runtime, org.eclipse.debug.core, org.eclipse.jdt.debug, org.eclipse.jdt.core, + org.eclipse.jdt.core.manipulation, org.eclipse.jdt.ls.core, org.eclipse.jdt.launching, com.google.gson, diff --git a/com.microsoft.java.debug.plugin/plugin.xml b/com.microsoft.java.debug.plugin/plugin.xml index 361cdcbda..69d76830d 100644 --- a/com.microsoft.java.debug.plugin/plugin.xml +++ b/com.microsoft.java.debug.plugin/plugin.xml @@ -20,6 +20,7 @@ + diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/InlineValueHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/InlineValueHandler.java new file mode 100644 index 000000000..5c2cf8834 --- /dev/null +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/InlineValueHandler.java @@ -0,0 +1,644 @@ +/******************************************************************************* +* Copyright (c) 2021 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.plugin.internal; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IBuffer; +import org.eclipse.jdt.core.IClassFile; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.jdt.core.ISourceReference; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.ITypeRoot; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; +import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; +import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.DoStatement; +import org.eclipse.jdt.core.dom.ForStatement; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.SwitchStatement; +import org.eclipse.jdt.core.dom.TypeDeclarationStatement; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.WhileStatement; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.IfStatement; +import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.LambdaExpression; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.ModuleDeclaration; +import org.eclipse.jdt.core.dom.PackageDeclaration; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.QualifiedType; +import org.eclipse.jdt.core.manipulation.CoreASTProvider; +import org.eclipse.jdt.ls.core.internal.JDTUtils; +import org.eclipse.jdt.ls.core.internal.handlers.JsonRpcHelpers; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; + +public class InlineValueHandler { + + /** + * Find the valid inline variables belonging to the visible view port. + */ + public static InlineVariable[] resolveInlineVariables(InlineParams params, IProgressMonitor monitor) { + ITypeRoot root = JDTUtils.resolveTypeRoot(params.uri); + try { + if (root == null || root.getBuffer() == null) { + return new InlineVariable[0]; + } + + Position stoppedLocation = params.stoppedLocation.getStart(); + int stoppedOffset = JsonRpcHelpers.toOffset(root.getBuffer(), stoppedLocation.getLine(), stoppedLocation.getCharacter()); + IMethod enclosingMethod = findEnclosingMethod(root, stoppedOffset); + if (enclosingMethod == null) { + return new InlineVariable[0]; + } + + Position startLocation = getPosition(root.getBuffer(), enclosingMethod.getSourceRange().getOffset()); + Range stoppedRange = new Range(startLocation, stoppedLocation); + if (params.viewPort != null + && (params.viewPort.getEnd().getLine() < startLocation.getLine() || params.viewPort.getStart().getLine() > stoppedLocation.getLine())) { + return new InlineVariable[0]; + } + + CompilationUnit astRoot = CoreASTProvider.getInstance().getAST(root, CoreASTProvider.WAIT_YES, monitor); + VariableVisitor visitor = new VariableVisitor(astRoot, stoppedRange, params.viewPort, Flags.isStatic(enclosingMethod.getFlags())); + astRoot.accept(visitor); + InlineVariable[] result = visitor.getInlineVariables(); + return result; + } catch (JavaModelException e) { + return new InlineVariable[0]; + } + } + + private static IMethod findEnclosingMethod(ITypeRoot root, int stoppedOffset) throws JavaModelException { + IType enclosingType = null; + if (root instanceof ICompilationUnit) { + IType[] types = ((ICompilationUnit) root).getAllTypes(); + for (IType type : types) { + if (isEnclosed(type, stoppedOffset)) { + enclosingType = type; + } + } + } else if (root instanceof IClassFile) { + enclosingType = ((IClassFile) root).getType(); + } + + if (enclosingType == null) { + return null; + } + + IMethod enclosingMethod = null; + for (IMethod method : enclosingType.getMethods()) { + if (isEnclosed(method, stoppedOffset)) { + enclosingMethod = method; + break; + } + } + + if (enclosingMethod == null) { + return null; + } + + // Deal with the scenario that the stopped location is inside the local types defined in method. + return findMethodInLocalTypes(enclosingMethod, stoppedOffset); + } + + private static boolean isEnclosed(ISourceReference sourceReference, int offset) throws JavaModelException { + ISourceRange sourceRange = sourceReference.getSourceRange(); + return sourceRange != null && offset >= sourceRange.getOffset() + && offset < sourceRange.getOffset() + sourceRange.getLength(); + } + + private static IMethod findMethodInLocalTypes(IMethod enclosingMethod, int stoppedOffset) throws JavaModelException { + if (enclosingMethod == null) { + return null; + } + + for (IJavaElement element : enclosingMethod.getChildren()) { + if (element instanceof IType) { + if (isEnclosed((IType) element, stoppedOffset)) { + for (IMethod method : ((IType) element).getMethods()) { + if (isEnclosed(method, stoppedOffset)) { + IMethod nearerMethod = findMethodInLocalTypes(method, stoppedOffset); + return nearerMethod == null ? enclosingMethod : nearerMethod; + } + } + + break; + } + } + } + + return enclosingMethod; + } + + /** + * Returns the zero based line and column number. + */ + private static Position getPosition(IBuffer buffer, int offset) { + int[] result = JsonRpcHelpers.toLine(buffer, offset); + if (result == null && result.length < 1) { + return new Position(-1, -1); + } + + return new Position(result[0], result[1]); + } + + static class VariableVisitor extends ASTVisitor { + private CompilationUnit unit = null; + private Range stoppedSourceRange; + private Range viewPort; + private boolean isStoppingAtStaticMethod; + private int baseLine; + private Set[] tokens; + private List localVarDecls = new ArrayList<>(); + private List localVarDeclPositions = new ArrayList<>(); + private boolean isStoppingAtLambda = false; + private Set varDeclsAtLastLine = new HashSet<>(); + private Range visibleInlineRange = null; + + public VariableVisitor(CompilationUnit unit, Range stoppedSourceRange, Range viewPort, boolean stopAtStaticMethod) { + this.unit = unit; + this.stoppedSourceRange = stoppedSourceRange; + this.viewPort = viewPort; + this.isStoppingAtStaticMethod = stopAtStaticMethod; + this.baseLine = stoppedSourceRange.getStart().getLine(); + this.tokens = new Set[stoppedSourceRange.getEnd().getLine() - stoppedSourceRange.getStart().getLine() + 1]; + updateVisibleRange(); + } + + private void updateVisibleRange() { + if (viewPort == null) { + visibleInlineRange = stoppedSourceRange; + } else if (compare(viewPort.getStart(), stoppedSourceRange.getEnd()) > 0 + || compare(viewPort.getEnd(), stoppedSourceRange.getStart()) < 0) { + visibleInlineRange = null; + } else { + Position start = compare(viewPort.getStart(), stoppedSourceRange.getStart()) >= 0 ? viewPort.getStart() : stoppedSourceRange.getStart(); + Position end = compare(viewPort.getEnd(), stoppedSourceRange.getEnd()) <= 0 ? viewPort.getEnd() : stoppedSourceRange.getEnd(); + visibleInlineRange = new Range(start, end); + } + } + + /** + * Handle the variables in the visible source ranges. + */ + @Override + public boolean visit(SimpleName node) { + if (visibleInlineRange == null) { + return false; + } + + Position startPosition = getStartPosition(node); + boolean isAtLastLine = isAtStopLocation(startPosition); + if (isEnclosed(visibleInlineRange, startPosition) || isAtLastLine) { + IBinding binding = node.resolveBinding(); + if (!(binding instanceof IVariableBinding)) { + return false; + } else if (isAtLastLine && this.varDeclsAtLastLine.contains(binding.getKey())) { + return false; + } + + String declaringClass = null; + if (((IVariableBinding) binding).isField()) { + ITypeBinding typeBinding = ((IVariableBinding) binding).getDeclaringClass(); + if (typeBinding == null) { + return false; + } + + declaringClass = typeBinding.getBinaryName(); + } + + Token token = new Token(node.getIdentifier(), startPosition, declaringClass); + int index = startPosition.getLine() - baseLine; + if (tokens[index] == null) { + tokens[index] = new LinkedHashSet<>(); + } + + if (!tokens[index].contains(token)) { + tokens[index].add(token); + } + } + + return false; + } + + /** + * Handle local variable declarations happening in current method. + */ + @Override + public boolean visit(VariableDeclarationFragment node) { + SimpleName name = node.getName(); + Position startPosition = getStartPosition(name); + if (isEnclosed(stoppedSourceRange, startPosition)) { + this.localVarDecls.add(name.getIdentifier()); + this.localVarDeclPositions.add(startPosition); + } + + if (isAtStopLocation(startPosition)) { + IVariableBinding binding = node.resolveBinding(); + if (binding != null) { + this.varDeclsAtLastLine.add(binding.getKey()); + } + } + + return true; + } + + /** + * Handle formal parameter declarations happening in current method. + */ + @Override + public boolean visit(SingleVariableDeclaration node) { + SimpleName name = node.getName(); + Position startPosition = getStartPosition(name); + if (isEnclosed(stoppedSourceRange, startPosition)) { + this.localVarDecls.add(name.getIdentifier()); + this.localVarDeclPositions.add(startPosition); + } + + return false; + } + + /** + * Handle the lambda expression containing the stopped location. + * If the execution instruction stops on a lambda expression, then + * crop the visible source ranges to the lambda expression body. + */ + @Override + public boolean visit(LambdaExpression node) { + Position startPosition = getStartPosition(node); + Position endPosition = getEndPosition(node); + if (compare(startPosition, stoppedSourceRange.getStart()) >= 0 + && isEnclosed(new Range(startPosition, endPosition), stoppedSourceRange.getEnd())) { + stoppedSourceRange.setStart(startPosition); + updateVisibleRange(); + isStoppingAtLambda = true; + localVarDecls.clear(); + localVarDeclPositions.clear(); + return true; + } + + return super.visit(node); + } + + /** + * Handle the method containing the stopped location. + */ + @Override + public boolean visit(MethodDeclaration node) { + Position startPosition = getStartPosition(node); + Position endPosition = getEndPosition(node); + if (compare(startPosition, stoppedSourceRange.getStart()) <= 0 && compare(endPosition, stoppedSourceRange.getEnd()) >= 0) { + return true; + } + + return false; + } + + @Override + public boolean visit(Block node) { + if (isUnreachableNode(node)) { + return false; + } + + return true; + } + + @Override + public boolean visit(DoStatement node) { + if (isUnreachableNode(node) && !isAtStopLocation(node)) { + return false; + } + + return super.visit(node); + } + + @Override + public boolean visit(ForStatement node) { + if (isUnreachableNode(node) && !isAtStopLocation(node)) { + return false; + } + + return super.visit(node); + } + + @Override + public boolean visit(IfStatement node) { + if (isUnreachableNode(node) && !isAtStopLocation(node)) { + return false; + } + + return super.visit(node); + } + + @Override + public boolean visit(SwitchStatement node) { + if (isUnreachableNode(node) && !isAtStopLocation(node)) { + return false; + } + + return super.visit(node); + } + + @Override + public boolean visit(WhileStatement node) { + if (isUnreachableNode(node) && !isAtStopLocation(node)) { + return false; + } + + return super.visit(node); + } + + @Override + public boolean visit(AnnotationTypeDeclaration node) { + if (isUnreachableNode(node)) { + return false; + } + + return super.visit(node); + } + + @Override + public boolean visit(AnonymousClassDeclaration node) { + if (isUnreachableNode(node)) { + return false; + } + + return super.visit(node); + } + + @Override + public boolean visit(TypeDeclarationStatement node) { + if (isUnreachableNode(node)) { + return false; + } + + return super.visit(node); + } + + @Override + public boolean visit(ImportDeclaration node) { + return false; + } + + @Override + public boolean visit(ModuleDeclaration node) { + return false; + } + + @Override + public boolean visit(PackageDeclaration node) { + return false; + } + + @Override + public boolean visit(QualifiedName node) { + return Objects.equals("length", node.getName().getIdentifier()); + } + + @Override + public boolean visit(QualifiedType node) { + return false; + } + + /** + * Return the valid inline variables in the visible source ranges. + * + *

There are four typical kinds of variable: + * - Local variables declared in method body. + * - Formal parameters declared in method declaration. + * - Field variables. + * - Captured variables from outer scope. This includes local type is accessing + * variables of enclosing method, and lambda expression body is accessing to + * variables of enclosing method.

+ * + *

For the first two kinds such as local variables and formal parameters, + * we're going to return them with VariableLookup kind since their values are + * expanded by Variables View by default.

+ * + *

For the last two kinds, we're going to return them with Evaluation kind + * since it requires additional evaluation to get its values.

+ */ + public InlineVariable[] getInlineVariables() { + if (visibleInlineRange == null) { + return new InlineVariable[0]; + } + + // Adding the local variable declarations to the token list. + for (int i = 0; i < localVarDecls.size(); i++) { + String name = localVarDecls.get(i); + Position position = localVarDeclPositions.get(i); + if (isEnclosed(visibleInlineRange, position)) { + int index = position.getLine() - baseLine; + if (tokens[index] == null) { + tokens[index] = new LinkedHashSet<>(); + } + + Token token = new Token(name, position, null); + if (!tokens[index].contains(token)) { + tokens[index].add(token); + } + } + } + + // For lambda expression in non static method, the captured variable 'arg$1' + // points to 'this' object of the enclosing method, and the index of other + // captured variables starts with 2. + int capturedArgIndexInLambda = isStoppingAtStaticMethod ? 1 : 2; + Map capturedVarsInLambda = new HashMap<>(); + List result = new ArrayList<>(); + for (int i = 0; i < tokens.length; i++) { + int line = baseLine + i; + if (tokens[i] == null || line < visibleInlineRange.getStart().getLine()) { + continue; + } + + for (Token token : tokens[i]) { + if (!isEnclosed(visibleInlineRange, token.position) && !isAtLastVisibleLine(token.position)) { + continue; + } + + // Local Variables + if (token.declaringClass == null && localVarDecls.contains(token.name)) { + int declIndex = localVarDecls.lastIndexOf(token.name); + Position declPosition = localVarDeclPositions.get(declIndex); + if (compare(token.position, declPosition) >= 0) { + result.add(new InlineVariable(new Range(token.position, token.position), token.name, InlineKind.VariableLookup)); + continue; + } + } + + InlineVariable value = new InlineVariable( + new Range(token.position, token.position), token.name, InlineKind.Evaluation, token.declaringClass); + // Captured variables by lambda expression + if (isStoppingAtLambda && token.declaringClass == null) { + /** + * When the lambda body accesses variables from its "outer" scope such as + * its enclosing method, these variables will be captured as properties of + * 'this' object of a synthetic lambda instance by Java runtime. However, + * when the compiler parses the lambda expression, it erases the specific + * variable name but keeps the captured variable names with format like + * 'arg$'. In order to evaluate the correct value from Java runtime, + * we have to encode the variable name using the same rule 'arg$' as + * the compiler. + */ + if (capturedVarsInLambda.containsKey(token.name)) { + value.expression = capturedVarsInLambda.get(token.name); + } else { + value.expression = "arg$" + capturedArgIndexInLambda++; + capturedVarsInLambda.put(token.name, value.expression); + } + } + + result.add(value); + } + } + + return result.toArray(new InlineVariable[0]); + } + + private Position getStartPosition(ASTNode node) { + // Line number returned by AST unit is one based, converts it to zero based. + int lineNumber = unit.getLineNumber(node.getStartPosition()) - 1; + int columnNumber = unit.getColumnNumber(node.getStartPosition()); + return new Position(lineNumber, columnNumber); + } + + private Position getEndPosition(ASTNode node) { + // Line number returned by AST unit is one based, converts it to zero based. + int lineNumber = unit.getLineNumber(node.getStartPosition() + node.getLength() - 1) - 1; + int columnNumber = unit.getColumnNumber(node.getStartPosition() + node.getLength() - 1); + return new Position(lineNumber, columnNumber); + } + + private boolean isUnreachableNode(ASTNode node) { + Position startPosition = getStartPosition(node); + Position endPosition = getEndPosition(node); + return compare(startPosition, stoppedSourceRange.getEnd()) > 0 + || compare(endPosition, stoppedSourceRange.getEnd()) < 0; + } + + private boolean isEnclosed(Range range, Position position) { + return compare(range.getStart(), position) <= 0 && compare(range.getEnd(), position) >= 0; + } + + private int compare(Position p1, Position p2) { + if (p1.getLine() < p2.getLine()) { + return -1; + } else if (p1.getLine() == p2.getLine()) { + return p1.getCharacter() - p2.getCharacter(); + } + + return 1; + } + + private boolean isAtStopLocation(Position position) { + return position.getLine() == stoppedSourceRange.getEnd().getLine(); + } + + private boolean isAtStopLocation(ASTNode node) { + Position startPosition = getStartPosition(node); + return isAtStopLocation(startPosition); + } + + private boolean isAtLastVisibleLine(Position position) { + return visibleInlineRange != null && visibleInlineRange.getEnd().getLine() == position.getLine(); + } + } + + static class InlineVariable { + Range range; + String name; + InlineKind kind; + String expression; + String declaringClass; + + public InlineVariable(Range range, String name, InlineKind kind) { + this.range = range; + this.name = name; + this.kind = kind; + } + + public InlineVariable(Range range, String name, InlineKind kind, String declaringClass) { + this.range = range; + this.name = name; + this.kind = kind; + this.declaringClass = declaringClass; + } + } + + static enum InlineKind { + VariableLookup, + Evaluation + } + + static class InlineParams { + String uri; + Range viewPort; + Range stoppedLocation; + } + + static class Token { + String name; + Position position; + String declaringClass = null; + + public Token(String name, Position position) { + this.name = name; + this.position = position; + } + + public Token(String name, Position position, String declaringClass) { + this.name = name; + this.position = position; + this.declaringClass = declaringClass; + } + + @Override + public int hashCode() { + return Objects.hash(declaringClass, name); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Token)) { + return false; + } + Token other = (Token) obj; + return Objects.equals(declaringClass, other.declaringClass) && Objects.equals(name, other.name); + } + } +} diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java index 032d0420c..1875d514e 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java @@ -28,6 +28,7 @@ import com.microsoft.java.debug.core.UsageDataStore; import com.microsoft.java.debug.core.protocol.JsonUtils; import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments; +import com.microsoft.java.debug.plugin.internal.InlineValueHandler.InlineParams; public class JavaDebugDelegateCommandHandler implements IDelegateCommandHandler { public static final String FETCH_USER_DATA = "vscode.java.fetchUsageData"; @@ -47,6 +48,7 @@ public class JavaDebugDelegateCommandHandler implements IDelegateCommandHandler public static final String FETCH_PLATFORM_SETTINGS = "vscode.java.fetchPlatformSettings"; public static final String RESOLVE_CLASSFILTERS = "vscode.java.resolveClassFilters"; public static final String RESOLVE_SOURCEURI = "vscode.java.resolveSourceUri"; + public static final String RESOLVE_INLINEVARIABLES = "vscode.java.resolveInlineVariables"; @Override public Object executeCommand(String commandId, List arguments, IProgressMonitor progress) throws Exception { @@ -90,6 +92,8 @@ public Object executeCommand(String commandId, List arguments, IProgress return JavaClassFilter.resolveClassFilters(arguments); case RESOLVE_SOURCEURI: return ResolveSourceMappingHandler.resolveSourceUri(arguments); + case RESOLVE_INLINEVARIABLES: + return InlineValueHandler.resolveInlineVariables(JsonUtils.fromJson((String) arguments.get(0), InlineParams.class), progress); default: break; } From f9aa9649b8c4a33b418a662b4516b2a14c977473 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Wed, 21 Apr 2021 17:26:12 +0800 Subject: [PATCH 5/7] Fix bug#973: breakpoints inside record don't work (#370) --- .../internal/JdtSourceLookUpProvider.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) 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 65ba6074a..d24d2abdd 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 @@ -61,6 +61,17 @@ public class JdtSourceLookUpProvider implements ISourceLookUpProvider { private ISourceContainer[] sourceContainers = null; private HashMap options = new HashMap(); + private String latestJavaVersion = null; + private int latestASTLevel; + + public JdtSourceLookUpProvider() { + // Get the latest supported Java version by JDT tooling. + this.latestJavaVersion = JavaCore.latestSupportedJavaVersion(); + // Get the mapped AST level for the latest Java version. + Map javaOptions = JavaCore.getOptions(); + javaOptions.put(JavaCore.COMPILER_SOURCE, latestJavaVersion); + this.latestASTLevel = new AST(javaOptions).apiLevel(); + } @Override public void initialize(IDebugAdapterContext context, Map props) { @@ -101,8 +112,7 @@ public String[] getFullyQualifiedName(String uri, int[] lines, int[] columns) th return new String[0]; } - // Currently the highest version the debugger supports is JavaSE-13 Edition (JLS13). - final ASTParser parser = ASTParser.newParser(AST.JLS13); + final ASTParser parser = ASTParser.newParser(this.latestASTLevel); parser.setResolveBindings(true); parser.setBindingsRecovery(true); parser.setStatementsRecovery(true); @@ -131,9 +141,10 @@ public String[] getFullyQualifiedName(String uri, int[] lines, int[] columns) th * the user need specify the compiler options explicitly. */ Map javaOptions = JavaCore.getOptions(); - javaOptions.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_13); - javaOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_13); - javaOptions.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_13); + 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 { From 6fc0528cff69403e470367973dbd93f9929d695e Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Thu, 22 Apr 2021 15:07:14 +0800 Subject: [PATCH 6/7] Refresh variables with new variable formatters (#369) --- .vscode/settings.json | 1 - .../java/debug/core/adapter/DebugAdapter.java | 2 + .../handler/RefreshVariablesHandler.java | 50 +++++++++++++++++++ .../java/debug/core/protocol/Events.java | 39 +++++++++++++++ .../java/debug/core/protocol/Requests.java | 9 ++++ 5 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RefreshVariablesHandler.java diff --git a/.vscode/settings.json b/.vscode/settings.json index fbaf055a0..2c67a2d4b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { - "java.configuration.updateBuildConfiguration": "automatic", "files.exclude": { "**/.git": true, "**/*.class": true, 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 db2b28adf..4342c48d3 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 @@ -32,6 +32,7 @@ import com.microsoft.java.debug.core.adapter.handler.InitializeRequestHandler; 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.RefreshVariablesHandler; import com.microsoft.java.debug.core.adapter.handler.RestartFrameHandler; import com.microsoft.java.debug.core.adapter.handler.ScopesRequestHandler; import com.microsoft.java.debug.core.adapter.handler.SetBreakpointsRequestHandler; @@ -123,6 +124,7 @@ private void initialize() { registerHandlerForDebug(new DataBreakpointInfoRequestHandler()); registerHandlerForDebug(new SetDataBreakpointsRequestHandler()); registerHandlerForDebug(new InlineValuesRequestHandler()); + registerHandlerForDebug(new RefreshVariablesHandler()); // NO_DEBUG mode only registerHandlerForNoDebug(new DisconnectRequestWithoutDebuggingHandler()); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RefreshVariablesHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RefreshVariablesHandler.java new file mode 100644 index 000000000..01214b615 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RefreshVariablesHandler.java @@ -0,0 +1,50 @@ +/******************************************************************************* +* Copyright (c) 2021 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.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.DebugSettings; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.protocol.Events.InvalidatedAreas; +import com.microsoft.java.debug.core.protocol.Events.InvalidatedEvent; +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.RefreshVariablesArguments; + +public class RefreshVariablesHandler implements IDebugRequestHandler { + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.REFRESHVARIABLES); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + RefreshVariablesArguments refreshArgs = (RefreshVariablesArguments) arguments; + if (refreshArgs != null) { + DebugSettings.getCurrent().showHex = refreshArgs.showHex; + DebugSettings.getCurrent().showQualifiedNames = refreshArgs.showQualifiedNames; + DebugSettings.getCurrent().showStaticVariables = refreshArgs.showStaticVariables; + DebugSettings.getCurrent().showLogicalStructure = refreshArgs.showLogicalStructure; + DebugSettings.getCurrent().showToString = refreshArgs.showToString; + } + + context.getProtocolServer().sendEvent(new InvalidatedEvent(InvalidatedAreas.VARIABLES)); + return CompletableFuture.completedFuture(response); + } +} 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 ea18f183c..5397c418e 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 @@ -11,6 +11,7 @@ package com.microsoft.java.debug.core.protocol; +import com.google.gson.annotations.SerializedName; import com.microsoft.java.debug.core.protocol.Types.Source; /** @@ -244,4 +245,42 @@ public UserNotificationEvent(NotificationType notifyType, String message) { this.message = message; } } + + public static enum InvalidatedAreas { + @SerializedName("all") + ALL, + @SerializedName("stacks") + STACKS, + @SerializedName("threads") + THREADS, + @SerializedName("variables") + VARIABLES; + } + + public static class InvalidatedEvent extends DebugEvent { + public InvalidatedAreas[] areas; + public long threadId; + public int frameId; + + public InvalidatedEvent() { + super("invalidated"); + } + + public InvalidatedEvent(InvalidatedAreas area) { + super("invalidated"); + this.areas = new InvalidatedAreas[]{area}; + } + + public InvalidatedEvent(InvalidatedAreas area, long threadId) { + super("invalidated"); + this.areas = new InvalidatedAreas[]{area}; + this.threadId = threadId; + } + + public InvalidatedEvent(InvalidatedAreas area, int frameId) { + super("invalidated"); + this.areas = new InvalidatedAreas[]{area}; + this.frameId = frameId; + } + } } 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 2afb02bd8..e31a65261 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 @@ -297,6 +297,14 @@ public static class SetVariableArguments extends Arguments { public ValueFormat format; } + public static class RefreshVariablesArguments extends Arguments { + public boolean showStaticVariables = false; + public boolean showQualifiedNames = false; + public boolean showHex = false; + public boolean showLogicalStructure = true; + public boolean showToString = true; + } + public static class SourceArguments extends Arguments { public int sourceReference; } @@ -401,6 +409,7 @@ public static enum Command { PAUSEALL("pauseAll", ThreadOperationArguments.class), PAUSEOTHERS("pauseOthers", ThreadOperationArguments.class), INLINEVALUES("inlineValues", InlineValuesArguments.class), + REFRESHVARIABLES("refreshVariables", RefreshVariablesArguments.class), UNSUPPORTED("", Arguments.class); private String command; From a0e31fc8bda7dbe604ef94aabd769bfb0a26bbbc Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Mon, 26 Apr 2021 09:46:49 +0800 Subject: [PATCH 7/7] Bump version to 0.31.0 (#371) --- com.microsoft.java.debug.core/pom.xml | 2 +- com.microsoft.java.debug.plugin/.classpath | 2 +- com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF | 4 ++-- com.microsoft.java.debug.plugin/pom.xml | 4 ++-- com.microsoft.java.debug.repository/category.xml | 2 +- com.microsoft.java.debug.repository/pom.xml | 2 +- pom.xml | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/com.microsoft.java.debug.core/pom.xml b/com.microsoft.java.debug.core/pom.xml index 7c20a8aae..04099cf90 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.30.1 + 0.31.0 com.microsoft.java.debug.core jar diff --git a/com.microsoft.java.debug.plugin/.classpath b/com.microsoft.java.debug.plugin/.classpath index e9e2f6c74..3fd9659f9 100644 --- a/com.microsoft.java.debug.plugin/.classpath +++ b/com.microsoft.java.debug.plugin/.classpath @@ -6,6 +6,6 @@ - + diff --git a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF index 40f78351f..be9168b0c 100644 --- a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF +++ b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ 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.30.1 +Bundle-Version: 0.31.0 Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ActivationPolicy: lazy Bundle-Activator: com.microsoft.java.debug.plugin.internal.JavaDebuggerServerPlugin @@ -25,4 +25,4 @@ Bundle-ClassPath: lib/commons-io-2.5.jar, ., lib/rxjava-2.1.1.jar, lib/reactive-streams-1.0.0.jar, - lib/com.microsoft.java.debug.core-0.30.1.jar + lib/com.microsoft.java.debug.core-0.31.0.jar diff --git a/com.microsoft.java.debug.plugin/pom.xml b/com.microsoft.java.debug.plugin/pom.xml index 2a1985e15..0fefc2fea 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.30.1 + 0.31.0 com.microsoft.java.debug.plugin eclipse-plugin @@ -45,7 +45,7 @@ com.microsoft.java com.microsoft.java.debug.core - 0.30.1 + 0.31.0 diff --git a/com.microsoft.java.debug.repository/category.xml b/com.microsoft.java.debug.repository/category.xml index d351c135e..9a33da2cf 100644 --- a/com.microsoft.java.debug.repository/category.xml +++ b/com.microsoft.java.debug.repository/category.xml @@ -1,6 +1,6 @@ - + diff --git a/com.microsoft.java.debug.repository/pom.xml b/com.microsoft.java.debug.repository/pom.xml index 09bd08f1b..1d0b490e9 100644 --- a/com.microsoft.java.debug.repository/pom.xml +++ b/com.microsoft.java.debug.repository/pom.xml @@ -4,7 +4,7 @@ com.microsoft.java java-debug-parent - 0.30.1 + 0.31.0 com.microsoft.java.debug.repository eclipse-repository diff --git a/pom.xml b/pom.xml index fe8385811..a27138fb9 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ ${base.name} :: Parent The Java Debug Server is an implementation of Visual Studio Code (VSCode) Debug Protocol. It can be used in Visual Studio Code to debug Java programs. https://github.com/Microsoft/java-debug - 0.30.1 + 0.31.0 pom Java Debug Server for Visual Studio Code