Bug 575551 - Fix evaluations at deeply nested intermediate lambda frames

The fix will add support to extract variables starting from correct
frame in the current call stack for lambda frames. Also this fix generic
variables with upper bound type variables.

Change-Id: I86a181df34c3a1045c4a30ec07b9dbcd9ab5c073
Signed-off-by: Gayan Perera <gayanper@gmail.com>
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.debug/+/189357
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/Bug575551.java b/org.eclipse.jdt.debug.tests/java8/Bug575551.java
new file mode 100644
index 0000000..930b981
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/java8/Bug575551.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class Bug575551 {
+	public void hoverOverLocal(String[] names) throws InterruptedException, ExecutionException {
+		CompletableFuture<List<String>> future = CompletableFuture.supplyAsync(() -> {
+			return Stream.of(names).filter(s -> {
+				try {
+					return CompletableFuture.supplyAsync(() -> {
+						return containsDigit(s.codePointAt(0), names.length);
+					}).get();
+				} catch (Exception e) {
+					return false;
+				}
+			}).collect(Collectors.toList());
+		});
+		future.get();
+	}
+	
+	private boolean containsDigit(int c, int length) {
+		return Bug575551.Character.isDigit(c) && c == length;
+	}
+	
+	public static void main(String[] args) throws InterruptedException, ExecutionException {
+		new Bug575551().hoverOverLocal(new String[] {"name"});
+	}
+	
+	public static class Character {
+		public static boolean isDigit(int c) {
+			return (new CharacterLatin()).isDigit(c);
+		}
+		
+		private static class CharacterLatin {
+			public boolean isDigit(int ch) {
+				return ch > 0;
+			}
+		}
+	}
+}
\ 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 f2fcc68..599ed29 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
@@ -500,6 +500,7 @@
 				cfgs.add(createLaunchConfiguration(jp, "Bug574395"));
 				cfgs.add(createLaunchConfiguration(jp, "Bug571310"));
 				cfgs.add(createLaunchConfiguration(jp, "Bug573547"));
+				cfgs.add(createLaunchConfiguration(jp, "Bug575551"));
 	    		loaded18 = true;
 	    		waitForBuild();
 	        }
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 351302a..39c8b3c 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
@@ -183,6 +183,23 @@
 		assertEquals("wrong result : ", "ab", value.getValueString());
 	}
 
+	public void testEvaluate_Bug575551_onIntermediateFrame_InsideLambda_OutFromMethodInvocationFrame() throws Exception {
+		createMethodBreakpoint("Bug575551$Character$CharacterLatin", "isDigit",
+				"(I)Z", true, false);
+		javaThread = launchToBreakpoint("Bug575551");
+		assertNotNull("The program did not suspend", javaThread);
+		// at method invocation stack
+		IValue value = doEval(javaThread, "ch");
+		assertEquals("wrong result at method stack : ", String.valueOf((int) 'n'), value.getValueString());
+
+		// at 1st lambda stack
+		value = doEval(javaThread, () -> (IJavaStackFrame) javaThread.getStackFrames()[3], "names.length");
+		assertEquals("wrong result at 1st lambda stack : ", "1", value.getValueString());
+
+		value = doEval(javaThread, () -> (IJavaStackFrame) javaThread.getStackFrames()[3], "s");
+		assertEquals("wrong result at 1st lambda stack : ", "name", value.getValueString());
+	}
+
 	private void debugWithBreakpoint(String testClass, int lineNumber) throws Exception {
 		createLineBreakpoint(lineNumber, testClass);
 		javaThread = launchToBreakpoint(testClass);
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 82f5ff9..2152c60 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
@@ -420,11 +420,11 @@
 
 	private void scanAndFixSignature(String genericSignature, String erasureSignature, StringBuilder fixedSignature) {
 		/*
-		 * This actually fix variables which are type of Generic Types which cannot be resolved to a type in the current content. For example variable
-		 * type like P_OUT in java.util.stream.ReferencePipeline.filter(Predicate<? super P_OUT>)
+		 * This actually fix variables which are type of Generic Types which cannot be resolved to a type in the current context.
+		 * For example variable type like P_OUT in java.util.stream.ReferencePipeline.filter(Predicate<? super P_OUT>)
 		 *
 		 * and also generic signature such as Ljava/util/function/Predicate<+Ljava/util/List<Ljava/lang/Integer;>;>; Ljava/util/Comparator<-TT;>;
-		 * which will fail the properly resolved to the type.
+		 * which will fail to properly resolved to the type.
 		 */
 		if (genericSignature.startsWith(String.valueOf(Signature.C_TYPE_VARIABLE)) ||
 				genericSignature.startsWith(String.valueOf(Signature.C_CAPTURE)) ||
@@ -444,8 +444,11 @@
 
 		String[] typeArguments = Signature.getTypeArguments(genericSignature);
 		if (typeArguments.length > 0) {
-			if (typeArguments.length == 1 && typeArguments[0].equals(String.valueOf(Signature.C_STAR))) {
-				// this is when we have recursive generics, so remove the generics to avoid compilation issues.
+			if (typeArguments.length == 1 &&
+					(typeArguments[0].equals(String.valueOf(Signature.C_STAR)) ||
+							typeArguments[0].startsWith(String.valueOf(new char[] { Signature.C_EXTENDS, Signature.C_TYPE_VARIABLE })))) {
+				// this is when we have recursive generics or we have a upper bound type variable
+				// so remove the generics to avoid compilation issues.
 				return;
 			}
 
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 ac0c551..6cf3803 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
@@ -18,6 +18,8 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.eclipse.debug.core.DebugException;
 import org.eclipse.debug.core.model.IStackFrame;
@@ -83,9 +85,10 @@
 		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];
+			// look for two frames below the frame which is provided instead starting from first frame.
+			List<IStackFrame> stackFrames = Stream.of(thread.getStackFrames()).dropWhile(f -> f != frame)
+					.limit(3).collect(Collectors.toUnmodifiableList());
+			for (IStackFrame stackFrame : stackFrames) {
 				IVariable[] stackFrameVariables = stackFrame.getVariables();
 				variables.addAll(Arrays.asList(stackFrameVariables));
 				for (IVariable frameVariable : stackFrameVariables) {