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
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/pom.xml b/com.microsoft.java.debug.core/pom.xml
index 24ec1b741..04099cf90 100644
--- a/com.microsoft.java.debug.core/pom.xml
+++ b/com.microsoft.java.debug.core/pom.xml
@@ -5,7 +5,7 @@
com.microsoft.javajava-debug-parent
- 0.29.0
+ 0.31.0com.microsoft.java.debug.corejar
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..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
@@ -30,7 +30,9 @@
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.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;
@@ -121,6 +123,8 @@ private void initialize() {
registerHandlerForDebug(new ExceptionInfoRequestHandler());
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/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/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.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 f62a241a1..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
@@ -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;
@@ -296,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;
}
@@ -340,6 +349,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 +408,8 @@ public static enum Command {
CONTINUEOTHERS("continueOthers", ThreadOperationArguments.class),
PAUSEALL("pauseAll", ThreadOperationArguments.class),
PAUSEOTHERS("pauseOthers", ThreadOperationArguments.class),
+ INLINEVALUES("inlineValues", InlineValuesArguments.class),
+ REFRESHVARIABLES("refreshVariables", RefreshVariablesArguments.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/.classpath b/com.microsoft.java.debug.plugin/.classpath
index 3291a02bb..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 905946192..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.29.0
+Bundle-Version: 0.31.0
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy
Bundle-Activator: com.microsoft.java.debug.plugin.internal.JavaDebuggerServerPlugin
@@ -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,
@@ -24,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.29.0.jar
+ lib/com.microsoft.java.debug.core-0.31.0.jar
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/pom.xml b/com.microsoft.java.debug.plugin/pom.xml
index 28901c18d..0fefc2fea 100644
--- a/com.microsoft.java.debug.plugin/pom.xml
+++ b/com.microsoft.java.debug.plugin/pom.xml
@@ -5,7 +5,7 @@
com.microsoft.javajava-debug-parent
- 0.29.0
+ 0.31.0com.microsoft.java.debug.plugineclipse-plugin
@@ -45,7 +45,7 @@
com.microsoft.javacom.microsoft.java.debug.core
- 0.29.0
+ 0.31.0
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