Skip to content

Commit fe77989

Browse files
Add support for lambda breakpoints (#427)
* Add support for lambda breakpoints The support for method header breakpoints was extended to support lambda breakpoints using the vscode inline breakpoint feature. * Optimize lambda search Only search for lambda when the breakpoing is an inline breakpoint. Add support for any column position within lambda expression. * Improve for multi line and multi lambdas * Improve lambda breakpoints - Only support for expressions. - The inline breakpoint should be before the expression start. * Remove the unnecessary nodeType check Co-authored-by: Jinbo Wang <[email protected]>
1 parent 8c0e87d commit fe77989

File tree

10 files changed

+231
-31
lines changed

10 files changed

+231
-31
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,19 @@ public String getCondition() {
115115

116116
@Override
117117
public boolean equals(Object obj) {
118-
if (!(obj instanceof IBreakpoint)) {
118+
if (!(obj instanceof Breakpoint)) {
119119
return super.equals(obj);
120120
}
121121

122-
IBreakpoint breakpoint = (IBreakpoint) obj;
123-
return Objects.equals(this.className(), breakpoint.className()) && this.getLineNumber() == breakpoint.getLineNumber();
122+
Breakpoint breakpoint = (Breakpoint) obj;
123+
return Objects.equals(this.className(), breakpoint.className())
124+
&& this.getLineNumber() == breakpoint.getLineNumber()
125+
&& Objects.equals(this.methodSignature, breakpoint.methodSignature);
126+
}
127+
128+
@Override
129+
public int hashCode() {
130+
return Objects.hash(this.className, this.lineNumber, this.methodSignature);
124131
}
125132

126133
@Override
@@ -298,7 +305,7 @@ private List<BreakpointRequest> createBreakpointRequests(List<ReferenceType> ref
298305
request.addCountFilter(hitCount);
299306
}
300307
request.enable();
301-
request.putProperty(IBreakpoint.REQUEST_TYPE_FUNCTIONAL, Boolean.valueOf(this.methodSignature != null));
308+
request.putProperty(IBreakpoint.REQUEST_TYPE, computeRequestType());
302309
newRequests.add(request);
303310
} catch (VMDisconnectedException ex) {
304311
// enable breakpoint operation may be executing while JVM is terminating, thus the VMDisconnectedException may be
@@ -310,6 +317,18 @@ private List<BreakpointRequest> createBreakpointRequests(List<ReferenceType> ref
310317
return newRequests;
311318
}
312319

320+
private Object computeRequestType() {
321+
if (this.methodSignature == null) {
322+
return IBreakpoint.REQUEST_TYPE_LINE;
323+
}
324+
325+
if (this.methodSignature.startsWith("lambda$")) {
326+
return IBreakpoint.REQUEST_TYPE_LAMBDA;
327+
} else {
328+
return IBreakpoint.REQUEST_TYPE_METHOD;
329+
}
330+
}
331+
313332
@Override
314333
public void putProperty(Object key, Object value) {
315334
propertyMap.put(key, value);

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/EvaluatableBreakpoint.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111

1212
package com.microsoft.java.debug.core;
1313

14-
import org.apache.commons.lang3.StringUtils;
15-
1614
import java.util.Map;
1715
import java.util.concurrent.CompletableFuture;
1816
import java.util.concurrent.ConcurrentHashMap;
1917

20-
import com.sun.jdi.event.ThreadDeathEvent;
18+
import org.apache.commons.lang3.StringUtils;
19+
2120
import com.sun.jdi.ThreadReference;
2221
import com.sun.jdi.VirtualMachine;
22+
import com.sun.jdi.event.ThreadDeathEvent;
2323

2424
import io.reactivex.disposables.Disposable;
2525

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IBreakpoint.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@
1515

1616
public interface IBreakpoint extends IDebugResource {
1717

18-
String REQUEST_TYPE_FUNCTIONAL = "functional";
18+
String REQUEST_TYPE = "request_type";
19+
20+
int REQUEST_TYPE_LINE = 0;
21+
22+
int REQUEST_TYPE_METHOD = 1;
23+
24+
int REQUEST_TYPE_LAMBDA = 2;
1925

2026
String className();
2127

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,24 @@ public static int convertLineNumber(int line, boolean sourceLinesStartAt1, boole
111111
}
112112
}
113113

114+
/**
115+
* Convert the source platform's column number to the target platform's column
116+
* number.
117+
*
118+
* @param column
119+
* the column number from the source platform
120+
* @param sourceColumnsStartAt1
121+
* the source platform's column starts at 1 or not
122+
* @return the new column number
123+
*/
124+
public static int convertColumnNumber(int column, boolean sourceColumnsStartAt1) {
125+
if (sourceColumnsStartAt1) {
126+
return column - 1;
127+
} else {
128+
return column;
129+
}
130+
}
131+
114132
/**
115133
* Convert the source platform's path format to the target platform's path format.
116134
*

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,12 @@ public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, bo
7979

8080
// Compute the breakpoints that are newly added.
8181
List<IBreakpoint> toAdd = new ArrayList<>();
82-
List<Integer> visitedLineNumbers = new ArrayList<>();
82+
List<Integer> visitedBreakpoints = new ArrayList<>();
8383
for (IBreakpoint breakpoint : breakpoints) {
84-
IBreakpoint existed = breakpointMap.get(String.valueOf(breakpoint.getLineNumber()));
84+
IBreakpoint existed = breakpointMap.get(String.valueOf(breakpoint.hashCode()));
8585
if (existed != null) {
8686
result.add(existed);
87-
visitedLineNumbers.add(existed.getLineNumber());
87+
visitedBreakpoints.add(existed.hashCode());
8888
continue;
8989
} else {
9090
result.add(breakpoint);
@@ -95,7 +95,7 @@ public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, bo
9595
// Compute the breakpoints that are no longer listed.
9696
List<IBreakpoint> toRemove = new ArrayList<>();
9797
for (IBreakpoint breakpoint : breakpointMap.values()) {
98-
if (!visitedLineNumbers.contains(breakpoint.getLineNumber())) {
98+
if (!visitedBreakpoints.contains(breakpoint.hashCode())) {
9999
toRemove.add(breakpoint);
100100
}
101101
}
@@ -113,7 +113,7 @@ private void addBreakpointsInternally(String source, IBreakpoint[] breakpoints)
113113
for (IBreakpoint breakpoint : breakpoints) {
114114
breakpoint.putProperty("id", this.nextBreakpointId.getAndIncrement());
115115
this.breakpoints.add(breakpoint);
116-
breakpointMap.put(String.valueOf(breakpoint.getLineNumber()), breakpoint);
116+
breakpointMap.put(String.valueOf(breakpoint.hashCode()), breakpoint);
117117
}
118118
}
119119
}
@@ -133,7 +133,7 @@ private void removeBreakpointsInternally(String source, IBreakpoint[] breakpoint
133133
// Destroy the breakpoint on the debugee VM.
134134
breakpoint.close();
135135
this.breakpoints.remove(breakpoint);
136-
breakpointMap.remove(String.valueOf(breakpoint.getLineNumber()));
136+
breakpointMap.remove(String.valueOf(breakpoint.hashCode()));
137137
} catch (Exception e) {
138138
logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e);
139139
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import com.sun.jdi.event.BreakpointEvent;
5353
import com.sun.jdi.event.Event;
5454
import com.sun.jdi.event.StepEvent;
55+
import com.sun.jdi.request.EventRequest;
5556

5657
public class SetBreakpointsRequestHandler implements IDebugRequestHandler {
5758

@@ -193,7 +194,8 @@ private void registerBreakpointHandler(IDebugAdapterContext context) {
193194

194195
// find the breakpoint related to this breakpoint event
195196
IBreakpoint expressionBP = getAssociatedEvaluatableBreakpoint(context, (BreakpointEvent) event);
196-
boolean functional = (boolean) event.request().getProperty(IBreakpoint.REQUEST_TYPE_FUNCTIONAL);
197+
String breakpointName = computeBreakpointName(event.request());
198+
197199
if (expressionBP != null) {
198200
CompletableFuture.runAsync(() -> {
199201
engine.evaluateForBreakpoint((IEvaluatableBreakpoint) expressionBP, bpThread).whenComplete((value, ex) -> {
@@ -205,20 +207,31 @@ private void registerBreakpointHandler(IDebugAdapterContext context) {
205207
debugEvent.eventSet.resume();
206208
} else {
207209
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
208-
functional ? "function breakpoint" : "breakpoint", bpThread.uniqueID()));
210+
breakpointName, bpThread.uniqueID()));
209211
}
210212
});
211213
});
212214
} else {
213215
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
214-
functional ? "function breakpoint" : "breakpoint", bpThread.uniqueID()));
216+
breakpointName, bpThread.uniqueID()));
215217
}
216218
debugEvent.shouldResume = false;
217219
}
218220
});
219221
}
220222
}
221223

224+
private String computeBreakpointName(EventRequest request) {
225+
switch ((int) request.getProperty(IBreakpoint.REQUEST_TYPE)) {
226+
case IBreakpoint.REQUEST_TYPE_LAMBDA:
227+
return "lambda breakpoint";
228+
case IBreakpoint.REQUEST_TYPE_METHOD:
229+
return "function breakpoint";
230+
default:
231+
return "breakpoint";
232+
}
233+
}
234+
222235
/**
223236
* Check whether the condition expression is satisfied, and return a boolean value to determine to resume the thread or not.
224237
*/
@@ -287,8 +300,13 @@ private IBreakpoint[] convertClientBreakpointsToDebugger(String sourceFile, Type
287300
int[] lines = Arrays.asList(sourceBreakpoints).stream().map(sourceBreakpoint -> {
288301
return AdapterUtils.convertLineNumber(sourceBreakpoint.line, context.isClientLinesStartAt1(), context.isDebuggerLinesStartAt1());
289302
}).mapToInt(line -> line).toArray();
303+
304+
int[] columns = Arrays.asList(sourceBreakpoints).stream().map(b -> {
305+
return AdapterUtils.convertColumnNumber(b.column, context.isClientColumnsStartAt1());
306+
}).mapToInt(b -> b).toArray();
307+
290308
ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class);
291-
String[] fqns = sourceProvider.getFullyQualifiedName(sourceFile, lines, null);
309+
String[] fqns = sourceProvider.getFullyQualifiedName(sourceFile, lines, columns);
292310
IBreakpoint[] breakpoints = new IBreakpoint[lines.length];
293311
for (int i = 0; i < lines.length; i++) {
294312
int hitCount = 0;

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ public Breakpoint(int id, boolean verified, int line, String message) {
202202

203203
public static class SourceBreakpoint {
204204
public int line;
205+
public int column;
205206
public String hitCondition;
206207
public String condition;
207208
public String logMessage;
@@ -217,6 +218,16 @@ public SourceBreakpoint(int line, String condition, String hitCondition) {
217218
this.condition = condition;
218219
this.hitCondition = hitCondition;
219220
}
221+
222+
/**
223+
* Constructor.
224+
*/
225+
public SourceBreakpoint(int line, String condition, String hitCondition, int column) {
226+
this.line = line;
227+
this.column = column;
228+
this.condition = condition;
229+
this.hitCondition = hitCondition;
230+
}
220231
}
221232

222233
public static class FunctionBreakpoint {

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ public class BreakpointLocationLocator
2020

2121
private IMethodBinding methodBinding;
2222

23-
public BreakpointLocationLocator(CompilationUnit compilationUnit, int lineNumber, boolean bindingsResolved,
23+
public BreakpointLocationLocator(CompilationUnit compilationUnit, int lineNumber,
24+
boolean bindingsResolved,
2425
boolean bestMatch) {
2526
super(compilationUnit, lineNumber, bindingsResolved, bestMatch);
2627
}
@@ -45,7 +46,7 @@ public String getMethodSignature() {
4546
if (this.methodBinding == null) {
4647
return null;
4748
}
48-
return toSignature(this.methodBinding);
49+
return toSignature(this.methodBinding, getMethodName());
4950
}
5051

5152
/**
@@ -70,13 +71,12 @@ public String getFullyQualifiedTypeName() {
7071
return super.getFullyQualifiedTypeName();
7172
}
7273

73-
private String toSignature(IMethodBinding binding) {
74+
static String toSignature(IMethodBinding binding, String name) {
7475
// use key for now until JDT core provides a public API for this.
7576
// "Ljava/util/Arrays;.asList<T:Ljava/lang/Object;>([TT;)Ljava/util/List<TT;>;"
7677
// "([Ljava/lang/String;)V|Ljava/lang/InterruptedException;"
7778
String signatureString = binding.getKey();
7879
if (signatureString != null) {
79-
String name = binding.getName();
8080
int index = signatureString.indexOf(name);
8181
if (index > -1) {
8282
int exceptionIndex = signatureString.indexOf("|", signatureString.lastIndexOf(")"));
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2022 Microsoft Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Gayan Perera ([email protected]) - initial API and implementation
10+
*******************************************************************************/
11+
package com.microsoft.java.debug;
12+
13+
import org.eclipse.jdt.core.dom.ASTNode;
14+
import org.eclipse.jdt.core.dom.ASTVisitor;
15+
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
16+
import org.eclipse.jdt.core.dom.CompilationUnit;
17+
import org.eclipse.jdt.core.dom.IMethodBinding;
18+
import org.eclipse.jdt.core.dom.LambdaExpression;
19+
20+
public class LambdaExpressionLocator extends ASTVisitor {
21+
private CompilationUnit compilationUnit;
22+
private int line;
23+
private int column;
24+
private boolean found;
25+
26+
private IMethodBinding lambdaMethodBinding;
27+
private LambdaExpression lambdaExpression;
28+
29+
public LambdaExpressionLocator(CompilationUnit compilationUnit, int line, int column) {
30+
this.compilationUnit = compilationUnit;
31+
this.line = line;
32+
this.column = column;
33+
}
34+
35+
@Override
36+
public boolean visit(LambdaExpression node) {
37+
// we only support inline breakpoints which are added before the expression part of the
38+
// lambda. And we don't support lambda blocks since they can be debugged using line
39+
// breakpoints.
40+
if (column > -1) {
41+
int startPosition = node.getStartPosition();
42+
int endPosition = node.getBody().getStartPosition();
43+
int offset = this.compilationUnit.getPosition(line, column);
44+
// lambda on same line:
45+
// list.stream().map(i -> i + 1);
46+
//
47+
// lambda on multiple lines:
48+
// list.stream().map(user
49+
// -> user.isSystem() ? new SystemUser(user) : new EndUser(user));
50+
51+
if (offset >= startPosition && offset <= endPosition) {
52+
this.lambdaMethodBinding = node.resolveMethodBinding();
53+
this.found = true;
54+
this.lambdaExpression = node;
55+
return false;
56+
}
57+
}
58+
return super.visit(node);
59+
}
60+
61+
/**
62+
* Returns <code>true</code> if a lambda is found at given location.
63+
*/
64+
public boolean isFound() {
65+
return found;
66+
}
67+
68+
/**
69+
* Returns the signature of lambda method otherwise return null.
70+
*/
71+
public String getMethodSignature() {
72+
if (!this.found) {
73+
return null;
74+
}
75+
return BreakpointLocationLocator.toSignature(this.lambdaMethodBinding, getMethodName());
76+
}
77+
78+
/**
79+
* Returns the name of lambda method otherwise return null.
80+
*/
81+
public String getMethodName() {
82+
if (!this.found) {
83+
return null;
84+
}
85+
String key = this.lambdaMethodBinding.getKey();
86+
return key.substring(key.indexOf('.') + 1, key.indexOf('('));
87+
}
88+
89+
/**
90+
* Returns the name of the type which the lambda method is found.
91+
*/
92+
public String getFullyQualifiedTypeName() {
93+
if (this.found) {
94+
ASTNode parent = lambdaExpression.getParent();
95+
while (parent != null) {
96+
if (parent instanceof AbstractTypeDeclaration) {
97+
AbstractTypeDeclaration declaration = (AbstractTypeDeclaration) parent;
98+
return declaration.resolveBinding().getBinaryName();
99+
}
100+
parent = parent.getParent();
101+
}
102+
}
103+
return null;
104+
}
105+
}

0 commit comments

Comments
 (0)