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$
 	}
-
-	}
+}