Skip to content

Commit 90e86a1

Browse files
testforstephenSarikaSinha
authored andcommitted
Bug 578145: Evaluations for lambda variables are not working in nested lambda
When the focus frame is a lambda frame, the evaluation engine will explicitly extract the visible variables from the target frame. Currently, it simply looks for the target frame and the following two frames below and adds their variables to the visible set. This doesn't work for cases like chained calls and nested lambda. An improved approach is to look for more frames in the same debug file which encloses the target frame, and append the variables of those enclosing frames to the visible set as well. This can deal with nested lambda better. Signed-off-by: Jinbo Wang <[email protected]> Change-Id: I57ab4fb2d0281951a693d05c8800f29d4584393f Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.debug/+/191713 Tested-by: JDT Bot <[email protected]> Reviewed-by: Gayan Perera <[email protected]>
1 parent 23fede5 commit 90e86a1

File tree

9 files changed

+278
-25
lines changed

9 files changed

+278
-25
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import java.util.Arrays;
2+
import java.util.List;
3+
4+
public class Bug578145LambdaInAnonymous {
5+
6+
public static void main(String[] args) {
7+
int numberInMain = 1;
8+
9+
new Runnable() {
10+
@Override
11+
public void run() {
12+
List<String> usersInAnonymous = Arrays.asList("Lambda");
13+
14+
usersInAnonymous.stream().forEach(u -> {
15+
int numberInLambda = 10;
16+
System.out.println("user name: " + u); // Add a breakpoint here
17+
});
18+
}
19+
}.run();
20+
}
21+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import java.util.Arrays;
2+
import java.util.List;
3+
4+
public class Bug578145LambdaInConstructor {
5+
List<String> names;
6+
7+
public Bug578145LambdaInConstructor(List<String> originalNames) {
8+
this.names = originalNames;
9+
int localInConstructor = 1;
10+
11+
this.names.stream().forEach((name) -> {
12+
int localInLambda = 10;
13+
System.out.println(name); // Add breakpoint here
14+
});
15+
}
16+
17+
public static void main(String[] args) {
18+
Bug578145LambdaInConstructor instance = new Bug578145LambdaInConstructor(Arrays.asList("Lambda"));
19+
}
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import java.util.function.Consumer;
2+
3+
public class Bug578145LambdaInFieldDeclaration {
4+
Runnable runnable = new Runnable() {
5+
@Override
6+
public void run() {
7+
int numberInRunnable = 1;
8+
9+
Consumer<Integer> myConsumer = (lambdaArg) -> {
10+
int numberInLambda = 10;
11+
System.out.println("id = " + lambdaArg); // Add breakpoint here
12+
};
13+
myConsumer.accept(numberInRunnable);
14+
}
15+
};
16+
17+
public static void main(String[] args) {
18+
Bug578145LambdaInFieldDeclaration instance = new Bug578145LambdaInFieldDeclaration();
19+
instance.runnable.run();
20+
}
21+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import java.util.Arrays;
2+
import java.util.List;
3+
4+
public class Bug578145LambdaInStaticInitializer {
5+
6+
static {
7+
int numberInStaticInitializer = 1;
8+
List<String> staticList = Arrays.asList("Lambda");
9+
10+
staticList.stream().forEach((name) -> {
11+
int numberInLambda = 10;
12+
System.out.println(name); // Add breakpoint here
13+
});
14+
}
15+
16+
public static void main(String[] args) {
17+
Bug578145LambdaInStaticInitializer instance = new Bug578145LambdaInStaticInitializer();
18+
}
19+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import java.util.Arrays;
2+
import java.util.List;
3+
4+
public class Bug578145LambdaOnChainCalls {
5+
public static void main(String[] args) {
6+
int numberInMain = 1;
7+
List<String> users = Arrays.asList("Lambda");
8+
9+
users.stream().forEach(u -> {
10+
int numberInLambda = 10;
11+
System.out.println("user name: " + u); // Add a breakpoint here
12+
});
13+
}
14+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import java.util.Arrays;
2+
import java.util.List;
3+
import java.util.function.Consumer;
4+
5+
public class Bug578145NestedLambda {
6+
7+
public static void main(String[] args) {
8+
int numberInMain = 1;
9+
10+
Consumer<Integer> myConsumer = (id) -> {
11+
int numberInExternalLambda = 10;
12+
13+
List<String> users = Arrays.asList("Lambda");
14+
users.stream().forEach(u -> {
15+
int numberInInnerLambda = 100;
16+
System.out.println("user name: " + u); // Add a breakpoint here
17+
});
18+
};
19+
myConsumer.accept(numberInMain);
20+
}
21+
}

org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2000, 2021 IBM Corporation and others.
2+
* Copyright (c) 2000, 2022 IBM Corporation and others.
33
*
44
* This program and the accompanying materials
55
* are made available under the terms of the Eclipse Public License 2.0
@@ -501,6 +501,12 @@ synchronized void assert18Project() {
501501
cfgs.add(createLaunchConfiguration(jp, "Bug571310"));
502502
cfgs.add(createLaunchConfiguration(jp, "Bug573547"));
503503
cfgs.add(createLaunchConfiguration(jp, "Bug575551"));
504+
cfgs.add(createLaunchConfiguration(jp, "Bug578145NestedLambda"));
505+
cfgs.add(createLaunchConfiguration(jp, "Bug578145LambdaInConstructor"));
506+
cfgs.add(createLaunchConfiguration(jp, "Bug578145LambdaInFieldDeclaration"));
507+
cfgs.add(createLaunchConfiguration(jp, "Bug578145LambdaInStaticInitializer"));
508+
cfgs.add(createLaunchConfiguration(jp, "Bug578145LambdaInAnonymous"));
509+
cfgs.add(createLaunchConfiguration(jp, "Bug578145LambdaOnChainCalls"));
504510
loaded18 = true;
505511
waitForBuild();
506512
}

org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/eval/LambdaVariableTest.java

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2020 Gayan Perera and others.
2+
* Copyright (c) 2020, 2022 Gayan Perera and others.
33
*
44
* This program and the accompanying materials
55
* are made available under the terms of the Eclipse Public License 2.0
@@ -200,6 +200,68 @@ public void testEvaluate_Bug575551_onIntermediateFrame_InsideLambda_OutFromMetho
200200
assertEquals("wrong result at 1st lambda stack : ", "name", value.getValueString());
201201
}
202202

203+
public void testEvaluate_Bug578145_NestedLambda() throws Exception {
204+
debugWithBreakpoint("Bug578145NestedLambda", 16);
205+
206+
IValue value = doEval(javaThread, "numberInInnerLambda");
207+
assertEquals("wrong result : ", "100", value.getValueString());
208+
209+
value = doEval(javaThread, "numberInExternalLambda");
210+
assertEquals("wrong result : ", "10", value.getValueString());
211+
212+
value = doEval(javaThread, "numberInMain");
213+
assertEquals("wrong result : ", "1", value.getValueString());
214+
}
215+
216+
public void testEvaluate_Bug578145_LambdaInConstructor() throws Exception {
217+
debugWithBreakpoint("Bug578145LambdaInConstructor", 13);
218+
IValue value = doEval(javaThread, "localInLambda");
219+
assertEquals("wrong result : ", "10", value.getValueString());
220+
221+
value = doEval(javaThread, "localInConstructor");
222+
assertEquals("wrong result : ", "1", value.getValueString());
223+
}
224+
225+
public void testEvaluate_Bug578145_LambdaInFieldDeclaration() throws Exception {
226+
debugWithBreakpoint("Bug578145LambdaInFieldDeclaration", 11);
227+
228+
IValue value = doEval(javaThread, "numberInLambda");
229+
assertEquals("wrong result : ", "10", value.getValueString());
230+
231+
value = doEval(javaThread, "numberInRunnable");
232+
assertEquals("wrong result : ", "1", value.getValueString());
233+
}
234+
235+
public void testEvaluate_Bug578145_LambdaInStaticInitializer() throws Exception {
236+
debugWithBreakpoint("Bug578145LambdaInStaticInitializer", 12);
237+
238+
IValue value = doEval(javaThread, "numberInLambda");
239+
assertEquals("wrong result : ", "10", value.getValueString());
240+
241+
value = doEval(javaThread, "numberInStaticInitializer");
242+
assertEquals("wrong result : ", "1", value.getValueString());
243+
}
244+
245+
public void testEvaluate_Bug578145_LambdaInAnonymous() throws Exception {
246+
debugWithBreakpoint("Bug578145LambdaInAnonymous", 16);
247+
248+
IValue value = doEval(javaThread, "numberInLambda");
249+
assertEquals("wrong result : ", "10", value.getValueString());
250+
251+
value = doEval(javaThread, "numberInMain");
252+
assertEquals("wrong result : ", "1", value.getValueString());
253+
}
254+
255+
public void testEvaluate_Bug578145_LambdaOnChainCalls() throws Exception {
256+
debugWithBreakpoint("Bug578145LambdaOnChainCalls", 11);
257+
258+
IValue value = doEval(javaThread, "numberInLambda");
259+
assertEquals("wrong result : ", "10", value.getValueString());
260+
261+
value = doEval(javaThread, "numberInMain");
262+
assertEquals("wrong result : ", "1", value.getValueString());
263+
}
264+
203265
private void debugWithBreakpoint(String testClass, int lineNumber) throws Exception {
204266
createLineBreakpoint(lineNumber, testClass);
205267
javaThread = launchToBreakpoint(testClass);

org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/LambdaUtils.java

Lines changed: 92 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2018, 2019 IBM Corporation and others.
2+
* Copyright (c) 2018, 2022 IBM Corporation and others.
33
*
44
* This program and the accompanying materials
55
* are made available under the terms of the Eclipse Public License 2.0
@@ -17,6 +17,7 @@
1717
import java.util.Arrays;
1818
import java.util.Collections;
1919
import java.util.List;
20+
import java.util.Objects;
2021
import java.util.stream.Collectors;
2122
import java.util.stream.Stream;
2223

@@ -30,6 +31,8 @@
3031
import org.eclipse.jdt.internal.debug.core.logicalstructures.JDILambdaVariable;
3132
import org.eclipse.jdt.internal.debug.eval.ast.engine.IRuntimeContext;
3233

34+
import com.sun.jdi.AbsentInformationException;
35+
import com.sun.jdi.Location;
3336
import com.sun.jdi.Method;
3437

3538
/**
@@ -41,11 +44,7 @@ public class LambdaUtils {
4144

4245
/**
4346
* 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
44-
* two frames below that frame.
45-
*
46-
* Inside a lambda expression, variable names are mangled by the compiler. Its therefore necessary to check the outer frame when at a lambda
47-
* 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
48-
* below the lambda frame. So in total we check 3 stack frames for the variable with the specified name.
47+
* outer frames visible from that frame.
4948
*
5049
* @param context
5150
* The context in which to check.
@@ -70,12 +69,11 @@ public static IVariable findLambdaFrameVariable(IRuntimeContext context, String
7069
}
7170

7271
/**
73-
* Collects variables visible from a lambda stack frame. I.e. inspects the specified stack frame; if that frame is a lambda frame, collects all
74-
* variables in that frame and two frames below that frame.
72+
* Collects variables visible from a lambda stack frame.
7573
*
76-
* Inside a lambda expression, variable names are mangled by the compiler. Its therefore necessary to check the outer frame when at a lambda
77-
* 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
78-
* below the lambda frame. So in total we collect variables from 3 stack frames.
74+
* If the debugging class generates all debugging info in its classfile (e.g. line number and source file name), we can use these info to find all
75+
* enclosing frames of the paused line number and collect their variables. Otherwise, collect variables from that lambda frame and two more frames
76+
* below it.
7977
*
8078
* @param frame
8179
* The lambda frame at which to check.
@@ -85,19 +83,90 @@ public static IVariable findLambdaFrameVariable(IRuntimeContext context, String
8583
* If accessing the top stack frame or the local variables on stack frames fails, due to failure to communicate with the debug target.
8684
*/
8785
public static List<IVariable> getLambdaFrameVariables(IStackFrame frame) throws DebugException {
88-
List<IVariable> variables = new ArrayList<>();
8986
if (LambdaUtils.isLambdaFrame(frame)) {
90-
IThread thread = frame.getThread();
91-
// look for two frames below the frame which is provided instead starting from first frame.
92-
List<IStackFrame> stackFrames = Stream.of(thread.getStackFrames()).dropWhile(f -> f != frame)
93-
.limit(3).collect(Collectors.toUnmodifiableList());
94-
for (IStackFrame stackFrame : stackFrames) {
95-
IVariable[] stackFrameVariables = stackFrame.getVariables();
96-
variables.addAll(Arrays.asList(stackFrameVariables));
97-
for (IVariable frameVariable : stackFrameVariables) {
98-
if (isLambdaObjectVariable(frameVariable)) {
99-
variables.addAll(extractVariablesFromLambda(frameVariable));
100-
}
87+
int lineNumber = frame.getLineNumber();
88+
String sourceName = ((IJavaStackFrame) frame).getSourceName();
89+
if (lineNumber == -1 || sourceName == null) {
90+
return collectVariablesFromLambdaFrame(frame);
91+
}
92+
return collectVariablesFromEnclosingFrames(frame);
93+
}
94+
return Collections.emptyList();
95+
}
96+
97+
/**
98+
* Collects variables visible from a lambda stack frame and two frames below that frame.
99+
*
100+
* Inside a lambda expression, variable names are mangled by the compiler. Its therefore necessary to check the outer frame when at a lambda
101+
* 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
102+
* below the lambda frame. So in total we collect variables from 3 stack frames.
103+
*
104+
* @param frame
105+
* The lambda frame at which to check.
106+
* @return The variables visible from the stack frame. The variables are ordered top-down, i.e. if shadowing occurs, the more local variable will
107+
* be first in the resulting list.
108+
* @throws DebugException
109+
* If accessing the top stack frame or the local variables on stack frames fails, due to failure to communicate with the debug target.
110+
*/
111+
private static List<IVariable> collectVariablesFromLambdaFrame(IStackFrame frame) throws DebugException {
112+
List<IVariable> variables = new ArrayList<>();
113+
IThread thread = frame.getThread();
114+
// look for two frames below the frame which is provided instead starting from first frame.
115+
List<IStackFrame> stackFrames = Stream.of(thread.getStackFrames()).dropWhile(f -> f != frame)
116+
.limit(3).collect(Collectors.toUnmodifiableList());
117+
for (IStackFrame stackFrame : stackFrames) {
118+
IVariable[] stackFrameVariables = stackFrame.getVariables();
119+
variables.addAll(Arrays.asList(stackFrameVariables));
120+
for (IVariable frameVariable : stackFrameVariables) {
121+
if (isLambdaObjectVariable(frameVariable)) {
122+
variables.addAll(extractVariablesFromLambda(frameVariable));
123+
}
124+
}
125+
}
126+
return Collections.unmodifiableList(variables);
127+
}
128+
129+
/**
130+
* Collect variables from all enclosing frames starting from the provided frame.
131+
*/
132+
private static List<IVariable> collectVariablesFromEnclosingFrames(IStackFrame frame) throws DebugException {
133+
List<IVariable> variables = new ArrayList<>();
134+
IThread thread = frame.getThread();
135+
List<IStackFrame> stackFrames = Stream.of(thread.getStackFrames()).dropWhile(f -> f != frame)
136+
.collect(Collectors.toUnmodifiableList());
137+
int pausedLineNumber = frame.getLineNumber();
138+
String pausedSourceName = ((IJavaStackFrame) frame).getSourceName();
139+
String pausedSourcePath = ((IJavaStackFrame) frame).getSourcePath();
140+
boolean isFocusFrame = true;
141+
for (IStackFrame stackFrame : stackFrames) {
142+
JDIStackFrame jdiFrame = (JDIStackFrame) stackFrame;
143+
if (isFocusFrame) {
144+
isFocusFrame = false;
145+
} else {
146+
if (!Objects.equals(pausedSourceName, jdiFrame.getSourceName())
147+
|| !Objects.equals(pausedSourcePath, jdiFrame.getSourcePath())) {
148+
continue;
149+
}
150+
List<Location> locations;
151+
try {
152+
locations = jdiFrame.getUnderlyingMethod().allLineLocations();
153+
} catch (AbsentInformationException e) {
154+
continue;
155+
}
156+
if (locations.isEmpty()) {
157+
continue;
158+
}
159+
int methodStartLine = locations.get(0).lineNumber();
160+
int methodEndLine = locations.get(locations.size() - 1).lineNumber();
161+
if (methodStartLine > pausedLineNumber || methodEndLine < pausedLineNumber) {
162+
continue;
163+
}
164+
}
165+
IVariable[] stackFrameVariables = jdiFrame.getVariables();
166+
variables.addAll(Arrays.asList(stackFrameVariables));
167+
for (IVariable frameVariable : stackFrameVariables) {
168+
if (isLambdaObjectVariable(frameVariable)) {
169+
variables.addAll(extractVariablesFromLambda(frameVariable));
101170
}
102171
}
103172
}

0 commit comments

Comments
 (0)