Bug 574395 - Evaluation support in intermediate lambda frames

This fix add support to evaluate at intermediate lambda frames by
resolving the lambda variables from the intermediate lambda frame.

Change-Id: I9b776efb75d70a1722177edbbd7cc5b09a29c95a
Signed-off-by: Gayan Perera <gayanper@gmail.com>
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.debug/+/182363
Tested-by: Sarika Sinha <sarika.sinha@in.ibm.com>
Tested-by: JDT Bot <jdt-bot@eclipse.org>
Reviewed-by: Sarika Sinha <sarika.sinha@in.ibm.com>
diff --git a/org.eclipse.jdt.debug.tests/java8/Bug574395.java b/org.eclipse.jdt.debug.tests/java8/Bug574395.java
new file mode 100644
index 0000000..9478a5a
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/java8/Bug574395.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Gayan Perera 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:
+ *     Gayan Perera - initial API and implementation
+ *******************************************************************************/
+import java.util.Arrays;
+import java.util.List;
+
+public class Bug574395 {
+	public static void main(String[] args) {
+		final List<String> list = Arrays.asList("1");
+		Arrays.asList(1, 2, 3).stream().filter(i -> {
+			return match(i, list);
+		}).count();
+	}
+
+	private static boolean match(Integer i, List<String> list) {
+		return list.contains(i);
+	}
+}
\ No newline at end of file
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 c5560d6..d8b5f63 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
@@ -496,6 +496,7 @@
 				cfgs.add(createLaunchConfiguration(jp, "Bug572629"));
 				cfgs.add(createLaunchConfiguration(jp, "Bug569413"));
 				cfgs.add(createLaunchConfiguration(jp, "Bug573589"));
+				cfgs.add(createLaunchConfiguration(jp, "Bug574395"));
 	    		loaded18 = true;
 	    		waitForBuild();
 	        }
@@ -2887,6 +2888,19 @@
 	 * @throws Exception
 	 */
 	protected IValue doEval(IJavaThread thread, String snippet) throws Exception{
+		return this.doEval(thread, () -> (IJavaStackFrame) thread.getTopStackFrame(), snippet);
+	}
+
+	/**
+	 * Perform the actual evaluation (inspect)
+	 *
+	 * @param thread
+	 * @param frameSupplier
+	 *            The frame supplier which provides the frame for the evaluation
+	 * @return the result of the evaluation
+	 * @throws Exception
+	 */
+	protected IValue doEval(IJavaThread thread, StackFrameSupplier frameSupplier, String snippet) throws Exception {
 		class Listener implements IEvaluationListener {
 			IEvaluationResult fResult;
 
@@ -2900,7 +2914,7 @@
 			}
 		}
 		Listener listener = new Listener();
-		IJavaStackFrame frame = (IJavaStackFrame) thread.getTopStackFrame();
+		IJavaStackFrame frame = frameSupplier.get();
 		assertNotNull("There should be a stackframe", frame);
 		ASTEvaluationEngine engine = new ASTEvaluationEngine(getProjectContext(), (IJavaDebugTarget) thread.getDebugTarget());
 		try {
@@ -2975,4 +2989,8 @@
 		}
 		return markersInfo.toString();
 	}
+
+	public interface StackFrameSupplier {
+		IJavaStackFrame get() throws Exception;
+	}
 }
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/eval/LambdaVariableTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/eval/LambdaVariableTest.java
index a76989a..351302a 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/eval/LambdaVariableTest.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/eval/LambdaVariableTest.java
@@ -16,6 +16,7 @@
 import org.eclipse.debug.core.model.IValue;
 import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.core.Signature;
+import org.eclipse.jdt.debug.core.IJavaStackFrame;
 import org.eclipse.jdt.debug.core.IJavaThread;
 import org.eclipse.jdt.debug.tests.AbstractDebugTest;
 
@@ -167,6 +168,13 @@
 		assertEquals("wrong result : ", "0", thisBasePackagesSize.getValueString());
 	}
 
+	public void testEvaluate_Bug574395_onIntermediateFrame_InsideLambda() throws Exception {
+		debugWithBreakpoint("Bug574395", 26);
+
+		IValue value = doEval(javaThread, () -> (IJavaStackFrame) javaThread.getStackFrames()[1], "match(i, list)");
+		assertEquals("wrong result : ", "false", value.getValueString());
+	}
+
 	public void testEvaluate_Bug569413_NestedLambdaCapturedParameterAndNull() throws Exception {
 		debugWithBreakpoint("Bug569413", 29);
 
diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/IRuntimeContext.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/IRuntimeContext.java
index bb0de02..521f93a 100644
--- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/IRuntimeContext.java
+++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/IRuntimeContext.java
@@ -14,11 +14,13 @@
 package org.eclipse.jdt.internal.debug.eval.ast.engine;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.DebugException;
 import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.debug.core.IJavaClassObject;
 import org.eclipse.jdt.debug.core.IJavaDebugTarget;
 import org.eclipse.jdt.debug.core.IJavaObject;
 import org.eclipse.jdt.debug.core.IJavaReferenceType;
+import org.eclipse.jdt.debug.core.IJavaStackFrame;
 import org.eclipse.jdt.debug.core.IJavaThread;
 import org.eclipse.jdt.debug.core.IJavaVariable;
 
@@ -130,4 +132,15 @@
 	 */
 	public IJavaClassObject classForName(String name) throws CoreException;
 
+	/**
+	 * Returns the stack frame in which the evaluation is performed on.
+	 *
+	 * @return stack frame
+	 * @throws DebugException
+	 *             if unable to load the stack frame.
+	 */
+	default IJavaStackFrame getFrame() throws DebugException {
+		return (IJavaStackFrame) getThread().getTopStackFrame();
+	}
+
 }
diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/RuntimeContext.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/RuntimeContext.java
index 8dd46da..6ad02a5 100644
--- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/RuntimeContext.java
+++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/RuntimeContext.java
@@ -85,7 +85,8 @@
 	 * @param frame
 	 *            the stack frame context used to compile/run expressions
 	 */
-	protected IJavaStackFrame getFrame() {
+	@Override
+	public IJavaStackFrame getFrame() {
 		return fFrame;
 	}
 
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 271ba79..ac0c551 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
@@ -26,7 +26,6 @@
 import org.eclipse.jdt.debug.core.IJavaFieldVariable;
 import org.eclipse.jdt.debug.core.IJavaObject;
 import org.eclipse.jdt.debug.core.IJavaStackFrame;
-import org.eclipse.jdt.debug.core.IJavaThread;
 import org.eclipse.jdt.internal.debug.core.logicalstructures.JDILambdaVariable;
 import org.eclipse.jdt.internal.debug.eval.ast.engine.IRuntimeContext;
 
@@ -53,10 +52,9 @@
 	 *             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);
+		IStackFrame stackFrame = context.getFrame();
+		if (stackFrame != null) {
+			List<IVariable> variables = getLambdaFrameVariables(stackFrame);
 			for (IVariable variable : variables) {
 				if (variable.getName().equals(variableName)) {
 					return variable;