Skip to content

Commit 1fcbe70

Browse files
authored
Improve support for method breakpoints (#426)
Now method breakpoints can added by adding a line breakpoint at method header.
1 parent 2461b03 commit 1fcbe70

File tree

5 files changed

+153
-17
lines changed

5 files changed

+153
-17
lines changed

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.concurrent.CompletableFuture;
1919

2020
import com.sun.jdi.Location;
21+
import com.sun.jdi.Method;
2122
import com.sun.jdi.ReferenceType;
2223
import com.sun.jdi.VMDisconnectedException;
2324
import com.sun.jdi.VirtualMachine;
@@ -38,6 +39,7 @@ public class Breakpoint implements IBreakpoint {
3839
private String condition = null;
3940
private String logMessage = null;
4041
private HashMap<Object, Object> propertyMap = new HashMap<>();
42+
private String methodSignature = null;
4143

4244
Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber) {
4345
this(vm, eventHub, className, lineNumber, 0, null);
@@ -50,7 +52,12 @@ public class Breakpoint implements IBreakpoint {
5052
Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, String condition) {
5153
this.vm = vm;
5254
this.eventHub = eventHub;
53-
this.className = className;
55+
if (className != null && className.contains("#")) {
56+
this.className = className.substring(0, className.indexOf("#"));
57+
this.methodSignature = className.substring(className.indexOf("#") + 1);
58+
} else {
59+
this.className = className;
60+
}
5461
this.lineNumber = lineNumber;
5562
this.hitCount = hitCount;
5663
this.condition = condition;
@@ -234,6 +241,24 @@ private static List<Location> collectLocations(List<ReferenceType> refTypes, int
234241
return locations;
235242
}
236243

244+
private static List<Location> collectLocations(List<ReferenceType> refTypes, String nameAndSignature) {
245+
List<Location> locations = new ArrayList<>();
246+
String[] segments = nameAndSignature.split("#");
247+
248+
for (ReferenceType refType : refTypes) {
249+
List<Method> methods = refType.methods();
250+
for (Method method : methods) {
251+
if (!method.isAbstract() && !method.isNative()
252+
&& segments[0].equals(method.name())
253+
&& (segments[1].equals(method.genericSignature()) || segments[1].equals(method.signature()))) {
254+
locations.add(method.location());
255+
break;
256+
}
257+
}
258+
}
259+
return locations;
260+
}
261+
237262
private List<BreakpointRequest> createBreakpointRequests(ReferenceType refType, int lineNumber, int hitCount,
238263
boolean includeNestedTypes) {
239264
List<ReferenceType> refTypes = new ArrayList<>();
@@ -243,7 +268,12 @@ private List<BreakpointRequest> createBreakpointRequests(ReferenceType refType,
243268

244269
private List<BreakpointRequest> createBreakpointRequests(List<ReferenceType> refTypes, int lineNumber,
245270
int hitCount, boolean includeNestedTypes) {
246-
List<Location> locations = collectLocations(refTypes, lineNumber, includeNestedTypes);
271+
List<Location> locations;
272+
if (this.methodSignature != null) {
273+
locations = collectLocations(refTypes, this.methodSignature);
274+
} else {
275+
locations = collectLocations(refTypes, lineNumber, includeNestedTypes);
276+
}
247277

248278
// find out the existing breakpoint locations
249279
List<Location> existingLocations = new ArrayList<>(requests.size());
@@ -268,6 +298,7 @@ private List<BreakpointRequest> createBreakpointRequests(List<ReferenceType> ref
268298
request.addCountFilter(hitCount);
269299
}
270300
request.enable();
301+
request.putProperty(IBreakpoint.REQUEST_TYPE_FUNCTIONAL, Boolean.valueOf(this.methodSignature != null));
271302
newRequests.add(request);
272303
} catch (VMDisconnectedException ex) {
273304
// enable breakpoint operation may be executing while JVM is terminating, thus the VMDisconnectedException may be

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
import java.util.concurrent.CompletableFuture;
1515

1616
public interface IBreakpoint extends IDebugResource {
17+
18+
String REQUEST_TYPE_FUNCTIONAL = "functional";
19+
1720
String className();
1821

1922
int getLineNumber();

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@
4444
import com.sun.jdi.BooleanValue;
4545
import com.sun.jdi.Field;
4646
import com.sun.jdi.ObjectReference;
47-
import com.sun.jdi.StringReference;
4847
import com.sun.jdi.ReferenceType;
48+
import com.sun.jdi.StringReference;
4949
import com.sun.jdi.ThreadReference;
50-
import com.sun.jdi.Value;
5150
import com.sun.jdi.VMDisconnectedException;
51+
import com.sun.jdi.Value;
5252
import com.sun.jdi.event.BreakpointEvent;
5353
import com.sun.jdi.event.Event;
5454
import com.sun.jdi.event.StepEvent;
@@ -171,6 +171,11 @@ private IBreakpoint getAssociatedEvaluatableBreakpoint(IDebugAdapterContext cont
171171
).findFirst().orElse(null);
172172
}
173173

174+
private IBreakpoint getAssociatedBreakpoint(IDebugAdapterContext context, BreakpointEvent event) {
175+
return Arrays.asList(context.getBreakpointManager().getBreakpoints()).stream()
176+
.filter(bp -> bp.requests().contains(event.request())).findFirst().orElse(null);
177+
}
178+
174179
private void registerBreakpointHandler(IDebugAdapterContext context) {
175180
IDebugSession debugSession = context.getDebugSession();
176181
if (debugSession != null) {
@@ -188,7 +193,7 @@ private void registerBreakpointHandler(IDebugAdapterContext context) {
188193

189194
// find the breakpoint related to this breakpoint event
190195
IBreakpoint expressionBP = getAssociatedEvaluatableBreakpoint(context, (BreakpointEvent) event);
191-
196+
boolean functional = (boolean) event.request().getProperty(IBreakpoint.REQUEST_TYPE_FUNCTIONAL);
192197
if (expressionBP != null) {
193198
CompletableFuture.runAsync(() -> {
194199
engine.evaluateForBreakpoint((IEvaluatableBreakpoint) expressionBP, bpThread).whenComplete((value, ex) -> {
@@ -199,12 +204,14 @@ private void registerBreakpointHandler(IDebugAdapterContext context) {
199204
if (resume) {
200205
debugEvent.eventSet.resume();
201206
} else {
202-
context.getProtocolServer().sendEvent(new Events.StoppedEvent("breakpoint", bpThread.uniqueID()));
207+
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
208+
functional ? "function breakpoint" : "breakpoint", bpThread.uniqueID()));
203209
}
204210
});
205211
});
206212
} else {
207-
context.getProtocolServer().sendEvent(new Events.StoppedEvent("breakpoint", bpThread.uniqueID()));
213+
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
214+
functional ? "function breakpoint" : "breakpoint", bpThread.uniqueID()));
208215
}
209216
debugEvent.shouldResume = false;
210217
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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.CompilationUnit;
14+
import org.eclipse.jdt.core.dom.IMethodBinding;
15+
import org.eclipse.jdt.core.dom.MethodDeclaration;
16+
17+
@SuppressWarnings("restriction")
18+
public class BreakpointLocationLocator
19+
extends org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator {
20+
21+
private IMethodBinding methodBinding;
22+
23+
public BreakpointLocationLocator(CompilationUnit compilationUnit, int lineNumber, boolean bindingsResolved,
24+
boolean bestMatch) {
25+
super(compilationUnit, lineNumber, bindingsResolved, bestMatch);
26+
}
27+
28+
@Override
29+
public boolean visit(MethodDeclaration node) {
30+
boolean result = super.visit(node);
31+
if (methodBinding == null && getLocationType() == LOCATION_METHOD) {
32+
this.methodBinding = node.resolveBinding();
33+
}
34+
return result;
35+
}
36+
37+
/**
38+
* Returns the signature of method found if the
39+
* {@link org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator#getLocationType()}
40+
* is
41+
* {@link org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator#LOCATION_METHOD}.
42+
* Otherwise return null.
43+
*/
44+
public String getMethodSignature() {
45+
if (this.methodBinding == null) {
46+
return null;
47+
}
48+
return toSignature(this.methodBinding);
49+
}
50+
51+
/**
52+
* Returns the name of method found if the
53+
* {@link org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator#getLocationType()}
54+
* is
55+
* {@link org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator#LOCATION_METHOD}.
56+
* Otherwise return null.
57+
*/
58+
public String getMethodName() {
59+
if (this.methodBinding == null) {
60+
return null;
61+
}
62+
return this.methodBinding.getName();
63+
}
64+
65+
@Override
66+
public String getFullyQualifiedTypeName() {
67+
if (this.methodBinding != null) {
68+
return this.methodBinding.getDeclaringClass().getQualifiedName();
69+
}
70+
return super.getFullyQualifiedTypeName();
71+
}
72+
73+
private String toSignature(IMethodBinding binding) {
74+
// use key for now until JDT core provides a public API for this.
75+
// "Ljava/util/Arrays;.asList<T:Ljava/lang/Object;>([TT;)Ljava/util/List<TT;>;"
76+
// "([Ljava/lang/String;)V|Ljava/lang/InterruptedException;"
77+
String signatureString = binding.getKey();
78+
if (signatureString != null) {
79+
String name = binding.getName();
80+
int index = signatureString.indexOf(name);
81+
if (index > -1) {
82+
int exceptionIndex = signatureString.indexOf("|", signatureString.lastIndexOf(")"));
83+
if (exceptionIndex > -1) {
84+
return signatureString.substring(index + name.length(), exceptionIndex);
85+
}
86+
return signatureString.substring(index + name.length());
87+
}
88+
}
89+
return null;
90+
}
91+
}

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,6 @@
2626
import java.util.logging.Level;
2727
import java.util.logging.Logger;
2828

29-
import com.microsoft.java.debug.core.Configuration;
30-
import com.microsoft.java.debug.core.DebugException;
31-
import com.microsoft.java.debug.core.adapter.AdapterUtils;
32-
import com.microsoft.java.debug.core.adapter.Constants;
33-
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
34-
import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider;
35-
3629
import org.eclipse.core.resources.IResource;
3730
import org.eclipse.core.runtime.CoreException;
3831
import org.eclipse.debug.core.sourcelookup.ISourceContainer;
@@ -48,11 +41,18 @@
4841
import org.eclipse.jdt.core.dom.ASTParser;
4942
import org.eclipse.jdt.core.dom.CompilationUnit;
5043
import org.eclipse.jdt.internal.core.JarPackageFragmentRoot;
51-
import org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator;
5244
import org.eclipse.jdt.launching.IVMInstall;
5345
import org.eclipse.jdt.launching.JavaRuntime;
5446
import org.eclipse.jdt.launching.LibraryLocation;
5547

48+
import com.microsoft.java.debug.BreakpointLocationLocator;
49+
import com.microsoft.java.debug.core.Configuration;
50+
import com.microsoft.java.debug.core.DebugException;
51+
import com.microsoft.java.debug.core.adapter.AdapterUtils;
52+
import com.microsoft.java.debug.core.adapter.Constants;
53+
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
54+
import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider;
55+
5656
public class JdtSourceLookUpProvider implements ISourceLookUpProvider {
5757
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
5858
private static final String JDT_SCHEME = "jdt";
@@ -162,12 +162,16 @@ public String[] getFullyQualifiedName(String uri, int[] lines, int[] columns) th
162162
// In current stage, we don't support to move the invalid breakpoint down to the next valid location, and just
163163
// mark it as "unverified".
164164
// In future, we could consider supporting to update the breakpoint to a valid location.
165-
ValidBreakpointLocationLocator locator = new ValidBreakpointLocationLocator(astUnit, lines[i], true, true);
165+
BreakpointLocationLocator locator = new BreakpointLocationLocator(astUnit, lines[i], true, true);
166166
astUnit.accept(locator);
167167
// When the final valid line location is same as the original line, that represents it's a valid breakpoint.
168168
// Add location type check to avoid breakpoint on method/field which will never be hit in current implementation.
169-
if (lines[i] == locator.getLineLocation() && locator.getLocationType() == ValidBreakpointLocationLocator.LOCATION_LINE) {
169+
if (lines[i] == locator.getLineLocation()
170+
&& locator.getLocationType() == BreakpointLocationLocator.LOCATION_LINE) {
170171
fqns[i] = locator.getFullyQualifiedTypeName();
172+
} else if (locator.getLocationType() == BreakpointLocationLocator.LOCATION_METHOD) {
173+
fqns[i] = locator.getFullyQualifiedTypeName().concat("#").concat(locator.getMethodName())
174+
.concat("#").concat(locator.getMethodSignature());
171175
}
172176
}
173177
}

0 commit comments

Comments
 (0)