Bug 404097 - Fixed some cases of breakpoint condition in lambda
This change fixes some cases of referring to an outer-scope variable in
a breakpoint condition within a lambda. Currently this leads to an error
dialog, indicating that the outer-scope varaible cannot be resolved.
With the fix, ASTEvaluationEngine will check whether the current frame
is a lambda frame. If so, it will also consider variables from two
frames above the current stack frame. This fixes the standard use case
of defining a lambda within a method and referring to local variables in
that method. More complex structures, such an anonymous class which
defines a lambda, are not addressed.
This change also adds test cases for the bug. The cases which fail are
disabled until a fix for the respective case is available.
Change-Id: I03e33d98952dfcfe2e1bb840cf6a65cf50a738d1
Signed-off-by: Simeon Andreev <simeon.danailov.andreev@gmail.com>
diff --git a/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointInAnonymousLocalClass.java b/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointInAnonymousLocalClass.java
new file mode 100644
index 0000000..08b6a64
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointInAnonymousLocalClass.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Simeon Andreev and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+
+import java.util.function.Consumer;
+
+public class Bug404097BreakpointInAnonymousLocalClass {
+
+ public static void breakpointMethod(final String methodParameter) {
+ // StringBuilder to make sure the compiler doesn't optimize "methodVariable" out of the compiled code.
+ final StringBuilder methodVariable = new StringBuilder("methodVariable");
+ Consumer<String> r = new Consumer<String>() {
+ public void accept(String lambdaParameter) {
+ String lambdaVariable = "lambdaVariable";
+ System.out.println("method parameter: " + methodParameter);
+ System.out.println("method variable: " + methodVariable);
+ System.out.println("lambda parameter: " + lambdaParameter);
+ System.out.println("lambda variable: " + lambdaVariable);
+ }
+ };
+ r.accept("lambdaParameter");
+ }
+
+ public static void main(String[] args) {
+ breakpointMethod("methodParameter");
+ }
+}
diff --git a/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointInLambda.java b/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointInLambda.java
new file mode 100644
index 0000000..158f975
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointInLambda.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Simeon Andreev and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+
+import java.util.function.Consumer;
+
+public class Bug404097BreakpointInLambda {
+
+ public static void breakpointMethod(final String methodParameter) {
+ // StringBuilder to make sure the compiler doesn't optimize "methodVariable" out of the compiled code.
+ final StringBuilder methodVariable = new StringBuilder("methodVariable");
+ Consumer<String> r = lambdaParameter -> {
+ String lambdaVariable = "lambdaVariable";
+ System.out.println("method parameter: " + methodParameter);
+ System.out.println("method variable: " + methodVariable);
+ System.out.println("lambda parameter: " + lambdaParameter);
+ System.out.println("lambda variable: " + lambdaVariable);
+ };
+ r.accept("lambdaParameter");
+ }
+
+ public static void main(String[] args) {
+ breakpointMethod("methodParameter");
+ }
+}
diff --git a/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointInLocalClass.java b/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointInLocalClass.java
new file mode 100644
index 0000000..029692c
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointInLocalClass.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Simeon Andreev and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+
+import java.util.function.Consumer;
+
+public class Bug404097BreakpointInLocalClass {
+
+ public static void breakpointMethod(final String methodParameter) {
+ // StringBuilder to make sure the compiler doesn't optimize "methodVariable" out of the compiled code.
+ final StringBuilder methodVariable = new StringBuilder("methodVariable");
+ class SomeConsumer implements Consumer<String> {
+ public void accept(String lambdaParameter) {
+ String lambdaVariable = "lambdaVariable";
+ System.out.println("method parameter: " + methodParameter);
+ System.out.println("method variable: " + methodVariable);
+ System.out.println("lambda parameter: " + lambdaParameter);
+ System.out.println("lambda variable: " + lambdaVariable);
+ }
+ };
+ Consumer<String> r = new SomeConsumer();
+ r.accept("lambdaParameter");
+ }
+
+ public static void main(String[] args) {
+ breakpointMethod("methodParameter");
+ }
+}
diff --git a/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointUsingInnerClass.java b/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointUsingInnerClass.java
new file mode 100644
index 0000000..224a73c
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointUsingInnerClass.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Simeon Andreev and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+
+public class Bug404097BreakpointUsingInnerClass {
+
+ static class InnerClass {
+ int i;
+ }
+
+ public static void breakpointMethod() {
+ InnerClass object = new InnerClass();
+ object.i = 0;
+ System.out.println(object.i);
+ }
+
+ public static void main(String[] args) {
+ breakpointMethod();
+ }
+}
diff --git a/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointUsingLocalClass.java b/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointUsingLocalClass.java
new file mode 100644
index 0000000..10b84b6
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/java8/Bug404097BreakpointUsingLocalClass.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Simeon Andreev and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+
+public class Bug404097BreakpointUsingLocalClass {
+
+ public static void breakpointMethod() {
+ class LocalClass {
+ int i;
+ }
+ LocalClass object = new LocalClass();
+ object.i = 0;
+ System.out.println(object.i);
+ }
+
+ public static void main(String[] args) {
+ breakpointMethod();
+ }
+}
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
index d654aa0..874c9a3 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
@@ -471,6 +471,11 @@
cfgs.add(createLaunchConfiguration(jp, "DebugHoverTest18"));
cfgs.add(createLaunchConfiguration(jp, "Bug541110"));
cfgs.add(createLaunchConfiguration(jp, "ClosureVariableTest_Bug542989"));
+ cfgs.add(createLaunchConfiguration(jp, "Bug404097BreakpointInLocalClass"));
+ cfgs.add(createLaunchConfiguration(jp, "Bug404097BreakpointInAnonymousLocalClass"));
+ cfgs.add(createLaunchConfiguration(jp, "Bug404097BreakpointInLambda"));
+ cfgs.add(createLaunchConfiguration(jp, "Bug404097BreakpointUsingInnerClass"));
+ cfgs.add(createLaunchConfiguration(jp, "Bug404097BreakpointUsingLocalClass"));
loaded18 = true;
waitForBuild();
}
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
index 4dab6bc..1828248 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
@@ -23,6 +23,7 @@
import org.eclipse.jdt.debug.tests.breakpoints.BreakpointListenerTests;
import org.eclipse.jdt.debug.tests.breakpoints.BreakpointLocationVerificationTests;
import org.eclipse.jdt.debug.tests.breakpoints.BreakpointWorkingSetTests;
+import org.eclipse.jdt.debug.tests.breakpoints.ConditionalBreakpointsInJava8Tests;
import org.eclipse.jdt.debug.tests.breakpoints.ConditionalBreakpointsTests;
import org.eclipse.jdt.debug.tests.breakpoints.ConditionalBreakpointsWithGenerics;
import org.eclipse.jdt.debug.tests.breakpoints.DeferredBreakpointTests;
@@ -349,6 +350,7 @@
if (JavaProjectHelper.isJava8Compatible()) {
addTest(new TestSuite(TestToggleBreakpointsTarget8.class));
addTest(new TestSuite(ModelPresentationTests18.class));
+ addTest(new TestSuite(ConditionalBreakpointsInJava8Tests.class));
}
if (JavaProjectHelper.isJava5Compatible()) {
addTest(new TestSuite(MethodBreakpointTests15.class));
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ConditionalBreakpointsInJava8Tests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ConditionalBreakpointsInJava8Tests.java
new file mode 100644
index 0000000..2f967cf
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ConditionalBreakpointsInJava8Tests.java
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Simeon Andreev and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.debug.tests.breakpoints;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.debug.core.DebugEvent;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchManager;
+import org.eclipse.debug.core.model.IDebugTarget;
+import org.eclipse.debug.internal.ui.views.console.ProcessConsole;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.debug.core.IJavaDebugTarget;
+import org.eclipse.jdt.debug.testplugin.DebugEventWaiter;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+import org.eclipse.jdt.debug.tests.TestUtil;
+
+/**
+ * Tests conditional breakpoints.
+ */
+public class ConditionalBreakpointsInJava8Tests extends AbstractDebugTest {
+
+ public ConditionalBreakpointsInJava8Tests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected IJavaProject getProjectContext() {
+ return get18Project();
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ assertNoErrorMarkersExist();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ terminateAndRemoveJavaLaunches();
+ removeAllBreakpoints();
+ super.tearDown();
+ }
+
+ /**
+ * Test for Bug 541110 - ClassCastException in Instruction.popValue and a zombie EventDispatcher$1 job afterwards
+ *
+ * We check that a specific conditional breakpoint on a line with a lambda expression does not cause a {@link ClassCastException}.
+ */
+ public void testBug541110() throws Exception {
+ String typeName = "Bug541110";
+ String breakpointCondition = "map.get(key) != null";
+ int breakpointLineNumber = 22;
+
+ // The class cast exception causes a job which runs forever. So we will timeout when waiting for debug events, if the exception occurs.
+ assertNoBreakpointHit(typeName, breakpointLineNumber, breakpointCondition);
+ }
+
+ /**
+ * Test for Bug 541110 - Cannot display or set conditional breakpoint using local class reference or field
+ *
+ * We check that a conditional breakpoint inside a locally defined anonymous class can access visbile variables.
+ */
+ public void testBug404097BreakpointInAnonymousLocalClass() throws Exception {
+ String typeName = "Bug404097BreakpointInAnonymousLocalClass";
+ int breakpointLineNumber = 25;
+ doTestVariableVisibility(typeName, breakpointLineNumber);
+ }
+
+ /**
+ * Test for Bug 541110 - Cannot display or set conditional breakpoint using local class reference or field
+ *
+ * We check that a conditional breakpoint inside a lambda can access visible variables.
+ */
+ public void testBug404097BreakpointInLambda() throws Exception {
+ String typeName = "Bug404097BreakpointInLambda";
+ int breakpointLineNumber = 24;
+ doTestVariableVisibility(typeName, breakpointLineNumber);
+ }
+
+ /**
+ * Test for Bug 541110 - Cannot display or set conditional breakpoint using local class reference or field
+ *
+ * We check that a conditional breakpoint inside a locally defined class can access visible variables.
+ */
+ public void testBug404097BreakpointInLocalClass() throws Exception {
+ String typeName = "Bug404097BreakpointInLocalClass";
+ int breakpointLineNumber = 25;
+ doTestVariableVisibility(typeName, breakpointLineNumber);
+ }
+
+ /**
+ * Test for Bug 541110 - Cannot display or set conditional breakpoint using local class reference or field
+ *
+ * We check that a conditional breakpoint can access members of a static inner class.
+ */
+ public void testBug404097BreakpointUsingInnerClass() throws Exception {
+ String typeName = "Bug404097BreakpointUsingInnerClass";
+ int breakpointLineNumber = 24;
+ doTestClassMemberVisibility(typeName, breakpointLineNumber);
+ }
+
+ /**
+ * Test for Bug 541110 - Cannot display or set conditional breakpoint using local class reference or field
+ *
+ * We check that a conditional breakpoint can access members of a class which is defined locally.
+ *
+ * TODO: disabled until a fix is available
+ */
+ public void disabled_testBug404097BreakpointUsingLocalClass() throws Exception {
+ String typeName = "Bug404097BreakpointUsingLocalClass";
+ int breakpointLineNumber = 23;
+ doTestClassMemberVisibility(typeName, breakpointLineNumber);
+ }
+
+ private void doTestVariableVisibility(String typeName, int breakpointLineNumber) throws Exception {
+ /*
+ * We create a condition which does not evaluate to true and expect to not hit the breakpoint.
+ * If condition evaluation runs into a compile error, either the breakpoint becomes unconditional and is therefore always hit.
+ */
+ String breakpointCondition = String.join(" || ", Arrays.asList(
+ "!\"methodParameter\".equals(methodParameter)",
+ "methodVariable == null", "!\"methodVariable\".equals(methodVariable.toString())",
+ "!\"lambdaParameter\".equals(lambdaParameter)",
+ "!\"lambdaVariable\".equals(lambdaVariable)"));
+ assertNoBreakpointHit(typeName, breakpointLineNumber, breakpointCondition);
+ }
+
+ private void doTestClassMemberVisibility(String typeName, int breakpointLineNumber) throws Exception {
+ /*
+ * We create a condition which does not evaluate to true and expect to not hit the breakpoint. If condition evaluation runs into a compile
+ * error, the breakpoint becomes unconditional and is therefore always hit.
+ */
+ String breakpointCondition = "object.i != 0";
+ assertNoBreakpointHit(typeName, breakpointLineNumber, breakpointCondition);
+ }
+
+ private void assertNoBreakpointHit(String typeName, int breakpointLineNumber, String breakpointCondition) throws Exception {
+ boolean suspendOnTrue = true;
+ createConditionalLineBreakpoint(breakpointLineNumber, typeName, breakpointCondition, suspendOnTrue);
+ ILaunchConfiguration config = getLaunchConfiguration(typeName);
+ DebugEventWaiter waiter = new DebugTargetTerminateWaiter();
+ launchAndWait(config, waiter);
+ TestUtil.waitForJobs(getName(), 1_000, 30_000, ProcessConsole.class);
+
+ }
+
+ private void terminateAndRemoveJavaLaunches() {
+ ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
+ List<ILaunch> launches = Arrays.asList(launchManager.getLaunches());
+ for (ILaunch launch : launches) {
+ IDebugTarget debugTarget = launch.getDebugTarget();
+ if (debugTarget instanceof IJavaDebugTarget) {
+ terminateAndRemove((IJavaDebugTarget) debugTarget);
+ }
+ }
+ }
+
+ private static class DebugTargetTerminateWaiter extends DebugEventWaiter {
+
+ public DebugTargetTerminateWaiter() {
+ super(DebugEvent.TERMINATE);
+ }
+
+ @Override
+ public boolean accept(DebugEvent event) {
+ if (super.accept(event)) {
+ Object source = event.getSource();
+ return source instanceof IDebugTarget;
+ }
+ return false;
+ }
+ }
+}
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/LambdaBreakpointsTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/LambdaBreakpointsTests.java
deleted file mode 100644
index 6d9f336..0000000
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/LambdaBreakpointsTests.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2018 Simeon Andreev and others.
- *
- * This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- * Simeon Andreev - initial API and implementation
- *******************************************************************************/
-package org.eclipse.jdt.debug.tests.breakpoints;
-
-import java.util.Arrays;
-import java.util.List;
-
-import org.eclipse.debug.core.DebugEvent;
-import org.eclipse.debug.core.DebugPlugin;
-import org.eclipse.debug.core.ILaunch;
-import org.eclipse.debug.core.ILaunchConfiguration;
-import org.eclipse.debug.core.ILaunchManager;
-import org.eclipse.debug.core.model.IDebugTarget;
-import org.eclipse.debug.internal.ui.views.console.ProcessConsole;
-import org.eclipse.jdt.core.IJavaProject;
-import org.eclipse.jdt.debug.core.IJavaDebugTarget;
-import org.eclipse.jdt.debug.testplugin.DebugEventWaiter;
-import org.eclipse.jdt.debug.tests.AbstractDebugTest;
-import org.eclipse.jdt.debug.tests.TestUtil;
-
-/**
- * Tests conditional breakpoints.
- */
-public class LambdaBreakpointsTests extends AbstractDebugTest {
-
- /**
- * Constructor
- * @param name
- */
- public LambdaBreakpointsTests(String name) {
- super(name);
- }
-
- @Override
- protected IJavaProject getProjectContext() {
- return get18Project();
- }
-
-
- /**
- * Test for Bug 541110 - ClassCastException in Instruction.popValue and a zombie EventDispatcher$1 job afterwards
- *
- * We check that a specific conditional breakpoint on a line with a lambda expression does not cause a {@link ClassCastException}.
- */
- public void testBug541110() throws Exception {
- assertNoErrorMarkersExist();
-
- String typeName = "Bug541110";
- createConditionalLineBreakpoint(22, typeName, "map.get(key) != null", true);
-
- try {
- // The class cast exception causes a job which runs forever. So we will timeout when waiting for debug events, if the exception occurs.
- ILaunchConfiguration config = getLaunchConfiguration(typeName);
- DebugEventWaiter waiter = new DebugEventWaiter(DebugEvent.TERMINATE);
- launchAndWait(config, waiter);
- // Join running jobs in case the launch did go through, but we have the endless job.
- TestUtil.waitForJobs(getName(), 1_000, 30_000, ProcessConsole.class);
- } finally {
- terminateAndRemoveJavaLaunches();
- removeAllBreakpoints();
- }
- }
-
- private void terminateAndRemoveJavaLaunches() {
- ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
- List<ILaunch> launches = Arrays.asList(launchManager.getLaunches());
- for (ILaunch launch : launches) {
- IDebugTarget debugTarget = launch.getDebugTarget();
- if (debugTarget instanceof IJavaDebugTarget) {
- terminateAndRemove((IJavaDebugTarget) debugTarget);
- }
- }
- }
-}
diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java
index d3b92c5..4a5f6ae 100644
--- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java
+++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java
@@ -15,8 +15,11 @@
*******************************************************************************/
package org.eclipse.jdt.internal.debug.eval.ast.engine;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
@@ -57,9 +60,13 @@
import org.eclipse.jdt.debug.eval.IEvaluationResult;
import org.eclipse.jdt.internal.debug.core.JDIDebugOptions;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
+import org.eclipse.jdt.internal.debug.core.logicalstructures.JDILambdaVariable;
+import org.eclipse.jdt.internal.debug.core.logicalstructures.JDIReturnValueVariable;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
+import org.eclipse.jdt.internal.debug.core.model.JDIThisVariable;
import org.eclipse.jdt.internal.debug.core.model.JDIThread;
import org.eclipse.jdt.internal.debug.core.model.JDIValue;
+import org.eclipse.jdt.internal.debug.core.model.LambdaUtils;
import org.eclipse.jdt.internal.debug.eval.EvaluationResult;
import org.eclipse.jdt.internal.debug.eval.ast.instructions.InstructionSequence;
@@ -284,7 +291,8 @@
EvaluationSourceGenerator mapper = null;
CompilationUnit unit = null;
try {
- IJavaVariable[] localsVar = context.getLocals();
+ List<IJavaVariable> localsVar = new ArrayList<>();
+ localsVar.addAll(Arrays.asList(context.getLocals()));
IJavaObject thisClass = context.getThis();
IVariable[] innerClassFields; // For anonymous classes, getting variables from outer class
if (null != thisClass) {
@@ -292,22 +300,41 @@
} else {
innerClassFields = new IVariable[0];
}
- int numLocalsVar = localsVar.length;
+ List<IVariable> lambdaFrameVariables = LambdaUtils.getLambdaFrameVariables(frame);
+ int numLocalsVar = localsVar.size();
Set<String> names = new HashSet<>();
// ******
// to hide problems with local variable declare as instance of Local
// Types
// and to remove locals with duplicate names
// IJavaVariable[] locals = new IJavaVariable[numLocalsVar];
- IJavaVariable[] locals = new IJavaVariable[numLocalsVar + innerClassFields.length];
- String[] localVariablesWithNull = new String[numLocalsVar + innerClassFields.length];
+ IJavaVariable[] locals = new IJavaVariable[numLocalsVar + innerClassFields.length + lambdaFrameVariables.size()];
+ String[] localVariablesWithNull = new String[numLocalsVar + innerClassFields.length + lambdaFrameVariables.size()];
int numLocals = 0;
for (int i = 0; i < numLocalsVar; i++) {
- if (!isLocalType(localsVar[i].getSignature())
- && !names.contains(localsVar[i].getName())) {
- locals[numLocals] = localsVar[i];
- names.add(localsVar[i].getName());
- localVariablesWithNull[numLocals++] = localsVar[i].getName();
+ IJavaVariable variable = localsVar.get(i);
+ if (!isLocalType(variable.getSignature()) && !names.contains(variable.getName())) {
+ locals[numLocals] = variable;
+ names.add(variable.getName());
+ localVariablesWithNull[numLocals++] = variable.getName();
+ }
+ }
+ /*
+ * If we are in a lambda frame, the variable context is not complete; names of outer-scope variables are mangled by the compiler. So we
+ * check variables one stack frame above the lambda frames, in order to also include outer-scope variables. This is necessary to use local
+ * variables defined in a method, within a breakpoint condition inside a lambda also defined in that method.
+ */
+ for (IVariable variable : lambdaFrameVariables) {
+ if (variable instanceof IJavaVariable && !isLambdaOrImplicitVariable(variable)) {
+ IJavaVariable javaVariable = (IJavaVariable) variable;
+ String variableName = variable.getName();
+ if (variableName != null && !variableName.contains("$")) { //$NON-NLS-1$
+ if (!isLocalType(javaVariable.getSignature()) && !names.contains(variableName)) {
+ locals[numLocals] = javaVariable;
+ names.add(variable.getName());
+ localVariablesWithNull[numLocals++] = variable.getName();
+ }
+ }
}
}
// Adding outer class variables to inner class scope
@@ -839,4 +866,10 @@
}
return updatedSnippet.toString();
}
+
+ private static boolean isLambdaOrImplicitVariable(IVariable variable) {
+ boolean isLambdaOrImplicitVariable = variable instanceof JDILambdaVariable || variable instanceof JDIReturnValueVariable
+ || variable instanceof JDIThisVariable;
+ return isLambdaOrImplicitVariable;
+ }
}
diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/PushLocalVariable.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/PushLocalVariable.java
index 69c1689..f7c3cee 100644
--- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/PushLocalVariable.java
+++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/PushLocalVariable.java
@@ -22,6 +22,7 @@
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaVariable;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
+import org.eclipse.jdt.internal.debug.core.model.LambdaUtils;
import org.eclipse.jdt.internal.debug.eval.ast.engine.ASTEvaluationEngine;
import org.eclipse.jdt.internal.debug.eval.ast.engine.IRuntimeContext;
import org.eclipse.osgi.util.NLS;
@@ -64,6 +65,11 @@
return;
}
}
+ IVariable variable = LambdaUtils.findLambdaFrameVariable(context, getName());
+ if (variable != null) {
+ push(variable);
+ return;
+ }
throw new CoreException(
new Status(
IStatus.ERROR,
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/LambdaUtils.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/LambdaUtils.java
index aeea804..851bc81 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/LambdaUtils.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/LambdaUtils.java
@@ -14,8 +14,18 @@
package org.eclipse.jdt.internal.debug.core.model;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
import org.eclipse.debug.core.DebugException;
+import org.eclipse.debug.core.model.IStackFrame;
+import org.eclipse.debug.core.model.IThread;
+import org.eclipse.debug.core.model.IVariable;
import org.eclipse.jdt.debug.core.IJavaStackFrame;
+import org.eclipse.jdt.debug.core.IJavaThread;
+import org.eclipse.jdt.internal.debug.eval.ast.engine.IRuntimeContext;
/**
* Utility class for Lambda Expressions and Stack frames Place holder for all Lambda operation encapsulation.
@@ -23,6 +33,77 @@
public class LambdaUtils {
/**
+ * Inspects the top stack frame of the context; if that frame is a lambda frame, looks for a variable with the specified name in that frame and
+ * two frames below that frame.
+ *
+ * Inside a lambda expression, variable names are mangled by the compiler. Its therefore necessary to check the outer frame when at a lambda
+ * frame, in order to find a variable with its name. The lambda expression itself is called by a synthetic static method, which is the first frame
+ * below the lambda frame. So in total we check 3 stack frames for the variable with the specified name.
+ *
+ * @param context
+ * The context in which to check.
+ * @param variableName
+ * The name of the variable.
+ * @return The variable with the specified name if found, {@code null} otherwise. Also returns {@code null} if no thread or top stack frame is
+ * available. If there are multiple variables with the same name in the lambda context, the most local one is returned.
+ * @throws DebugException
+ * If accessing the top stack frame or the local variables on stack frames fails, due to failure to communicate with the debug target.
+ */
+ public static IVariable findLambdaFrameVariable(IRuntimeContext context, String variableName) throws DebugException {
+ IJavaThread thread = context.getThread();
+ if (thread != null) {
+ IStackFrame topStackFrame = thread.getTopStackFrame();
+ List<IVariable> variables = getLambdaFrameVariables(topStackFrame);
+ for (IVariable variable : variables) {
+ if (variable.getName().equals(variableName)) {
+ return variable;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Collects variables visible from a lambda stack frame. I.e. inspects the specified stack frame; if that frame is a lambda frame, collects all
+ * variables in that frame and two frames below that frame.
+ *
+ * Inside a lambda expression, variable names are mangled by the compiler. Its therefore necessary to check the outer frame when at a lambda
+ * frame, in order to find a variable with its name. The lambda expression itself is called by a synthetic static method, which is the first frame
+ * below the lambda frame. So in total we collect variables from 3 stack frames.
+ *
+ * @param frame
+ * The lambda frame at which to check.
+ * @return The variables visible from the stack frame. An empty list if the specified stack frame is not a lambda frame. The variables are ordered
+ * top-down, i.e. if shadowing occurs, the more local variable will be first in the resulting list.
+ * @throws DebugException
+ * If accessing the top stack frame or the local variables on stack frames fails, due to failure to communicate with the debug target.
+ */
+ public static List<IVariable> getLambdaFrameVariables(IStackFrame frame) throws DebugException {
+ List<IVariable> variables = new ArrayList<>();
+ if (LambdaUtils.isLambdaFrame(frame)) {
+ IThread thread = frame.getThread();
+ IStackFrame[] stackFrames = thread.getStackFrames();
+ for (int i = 0; i < Math.min(3, stackFrames.length); ++i) {
+ IStackFrame stackFrame = stackFrames[i];
+ IVariable[] stackFrameVariables = stackFrame.getVariables();
+ variables.addAll(Arrays.asList(stackFrameVariables));
+ }
+ }
+ return Collections.unmodifiableList(variables);
+ }
+
+ /**
+ * Evaluates if the input frame is a lambda frame.
+ *
+ * @param frame
+ * the frame which needs to be evaluated
+ * @return <code>True</code> if the frame is a lambda frame else return <code>False</Code>
+ */
+ public static boolean isLambdaFrame(IStackFrame frame) throws DebugException {
+ return frame instanceof IJavaStackFrame && isLambdaFrame((IJavaStackFrame) frame);
+ }
+
+ /**
* Evaluates if the input frame is a lambda frame.
*
* @param frame
@@ -33,5 +114,4 @@
public static boolean isLambdaFrame(IJavaStackFrame frame) throws DebugException {
return frame.isSynthetic() && frame.getName().startsWith("lambda$"); //$NON-NLS-1$
}
-
- }
+}