Skip to content

Commit 9bb9832

Browse files
Support method breakpoints (#424)
* Add support for method breakpoints * Handle the duplicated MethodEntryEvent better Co-authored-by: Gayan Perera <[email protected]>
1 parent 57a98e2 commit 9bb9832

File tree

10 files changed

+571
-2
lines changed

10 files changed

+571
-2
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,10 @@ public IEventHub getEventHub() {
146146
public VirtualMachine getVM() {
147147
return vm;
148148
}
149+
150+
@Override
151+
public IMethodBreakpoint createFunctionBreakpoint(String className, String functionName, String condition,
152+
int hitCount) {
153+
return new MethodBreakpoint(vm, this.getEventHub(), className, functionName, condition, hitCount);
154+
}
149155
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public interface IDebugSession {
3636

3737
void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters);
3838

39-
// TODO: createFunctionBreakpoint
39+
IMethodBreakpoint createFunctionBreakpoint(String className, String functionName, String condition, int hitCount);
4040

4141
Process process();
4242

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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 - initial API and implementation
10+
*******************************************************************************/
11+
package com.microsoft.java.debug.core;
12+
13+
import java.util.concurrent.CompletableFuture;
14+
15+
public interface IMethodBreakpoint extends IDebugResource {
16+
String methodName();
17+
18+
String className();
19+
20+
int getHitCount();
21+
22+
String getCondition();
23+
24+
void setHitCount(int hitCount);
25+
26+
void setCondition(String condition);
27+
28+
CompletableFuture<IMethodBreakpoint> install();
29+
30+
Object getProperty(Object key);
31+
32+
void putProperty(Object key, Object value);
33+
}
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
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 - initial API and implementation
10+
*******************************************************************************/
11+
package com.microsoft.java.debug.core;
12+
13+
import java.util.ArrayList;
14+
import java.util.HashMap;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.Objects;
18+
import java.util.Optional;
19+
import java.util.concurrent.CompletableFuture;
20+
import java.util.concurrent.ConcurrentHashMap;
21+
22+
import org.apache.commons.lang3.StringUtils;
23+
24+
import com.sun.jdi.ReferenceType;
25+
import com.sun.jdi.ThreadReference;
26+
import com.sun.jdi.VMDisconnectedException;
27+
import com.sun.jdi.VirtualMachine;
28+
import com.sun.jdi.event.ClassPrepareEvent;
29+
import com.sun.jdi.event.ThreadDeathEvent;
30+
import com.sun.jdi.request.ClassPrepareRequest;
31+
import com.sun.jdi.request.EventRequest;
32+
import com.sun.jdi.request.MethodEntryRequest;
33+
34+
import io.reactivex.Observable;
35+
import io.reactivex.disposables.Disposable;
36+
37+
public class MethodBreakpoint implements IMethodBreakpoint, IEvaluatableBreakpoint {
38+
39+
private VirtualMachine vm;
40+
private IEventHub eventHub;
41+
private String className;
42+
private String functionName;
43+
private String condition;
44+
private int hitCount;
45+
46+
private HashMap<Object, Object> propertyMap = new HashMap<>();
47+
private Object compiledConditionalExpression = null;
48+
private Map<Long, Object> compiledExpressions = new ConcurrentHashMap<>();
49+
50+
private List<EventRequest> requests = new ArrayList<>();
51+
private List<Disposable> subscriptions = new ArrayList<>();
52+
53+
public MethodBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, String functionName,
54+
String condition, int hitCount) {
55+
Objects.requireNonNull(vm);
56+
Objects.requireNonNull(eventHub);
57+
Objects.requireNonNull(className);
58+
Objects.requireNonNull(functionName);
59+
this.vm = vm;
60+
this.eventHub = eventHub;
61+
this.className = className;
62+
this.functionName = functionName;
63+
this.condition = condition;
64+
this.hitCount = hitCount;
65+
}
66+
67+
@Override
68+
public List<EventRequest> requests() {
69+
return requests;
70+
}
71+
72+
@Override
73+
public List<Disposable> subscriptions() {
74+
return subscriptions;
75+
}
76+
77+
@Override
78+
public void close() throws Exception {
79+
try {
80+
vm.eventRequestManager().deleteEventRequests(requests());
81+
} catch (VMDisconnectedException ex) {
82+
// ignore since removing breakpoints is meaningless when JVM is terminated.
83+
}
84+
subscriptions().forEach(Disposable::dispose);
85+
requests.clear();
86+
subscriptions.clear();
87+
}
88+
89+
@Override
90+
public boolean containsEvaluatableExpression() {
91+
return containsConditionalExpression() || containsLogpointExpression();
92+
}
93+
94+
@Override
95+
public boolean containsConditionalExpression() {
96+
return StringUtils.isNotBlank(getCondition());
97+
}
98+
99+
@Override
100+
public boolean containsLogpointExpression() {
101+
return false;
102+
}
103+
104+
@Override
105+
public String getCondition() {
106+
return condition;
107+
}
108+
109+
@Override
110+
public void setCondition(String condition) {
111+
this.condition = condition;
112+
setCompiledConditionalExpression(null);
113+
compiledExpressions.clear();
114+
}
115+
116+
@Override
117+
public String getLogMessage() {
118+
return null;
119+
}
120+
121+
@Override
122+
public void setLogMessage(String logMessage) {
123+
// for future implementation
124+
}
125+
126+
@Override
127+
public void setCompiledConditionalExpression(Object compiledExpression) {
128+
this.compiledConditionalExpression = compiledExpression;
129+
}
130+
131+
@Override
132+
public Object getCompiledConditionalExpression() {
133+
return compiledConditionalExpression;
134+
}
135+
136+
@Override
137+
public void setCompiledLogpointExpression(Object compiledExpression) {
138+
// for future implementation
139+
}
140+
141+
@Override
142+
public Object getCompiledLogpointExpression() {
143+
return null;
144+
}
145+
146+
@Override
147+
public void setCompiledExpression(long threadId, Object compiledExpression) {
148+
compiledExpressions.put(threadId, compiledExpression);
149+
}
150+
151+
@Override
152+
public Object getCompiledExpression(long threadId) {
153+
return compiledExpressions.get(threadId);
154+
}
155+
156+
@Override
157+
public int getHitCount() {
158+
return hitCount;
159+
}
160+
161+
@Override
162+
public void setHitCount(int hitCount) {
163+
this.hitCount = hitCount;
164+
Observable.fromIterable(this.requests())
165+
.filter(request -> request instanceof MethodEntryRequest)
166+
.subscribe(request -> {
167+
request.addCountFilter(hitCount);
168+
request.enable();
169+
});
170+
}
171+
172+
@Override
173+
public CompletableFuture<IMethodBreakpoint> install() {
174+
Disposable subscription = eventHub.events()
175+
.filter(debugEvent -> debugEvent.event instanceof ThreadDeathEvent)
176+
.subscribe(debugEvent -> {
177+
ThreadReference deathThread = ((ThreadDeathEvent) debugEvent.event).thread();
178+
compiledExpressions.remove(deathThread.uniqueID());
179+
});
180+
181+
subscriptions.add(subscription);
182+
183+
// It's possible that different class loaders create new class with the same
184+
// name.
185+
// Here to listen to future class prepare events to handle such case.
186+
ClassPrepareRequest classPrepareRequest = vm.eventRequestManager().createClassPrepareRequest();
187+
classPrepareRequest.addClassFilter(className);
188+
classPrepareRequest.enable();
189+
requests.add(classPrepareRequest);
190+
191+
CompletableFuture<IMethodBreakpoint> future = new CompletableFuture<>();
192+
subscription = eventHub.events()
193+
.filter(debugEvent -> debugEvent.event instanceof ClassPrepareEvent
194+
&& (classPrepareRequest.equals(debugEvent.event.request())))
195+
.subscribe(debugEvent -> {
196+
ClassPrepareEvent event = (ClassPrepareEvent) debugEvent.event;
197+
Optional<MethodEntryRequest> createdRequest = createMethodEntryRequest(event.referenceType());
198+
if (createdRequest.isPresent()) {
199+
MethodEntryRequest methodEntryRequest = createdRequest.get();
200+
requests.add(methodEntryRequest);
201+
if (!future.isDone()) {
202+
this.putProperty("verified", true);
203+
future.complete(this);
204+
}
205+
}
206+
});
207+
subscriptions.add(subscription);
208+
209+
List<ReferenceType> types = vm.classesByName(className);
210+
for (ReferenceType type : types) {
211+
Optional<MethodEntryRequest> createdRequest = createMethodEntryRequest(type);
212+
if (createdRequest.isPresent()) {
213+
MethodEntryRequest methodEntryRequest = createdRequest.get();
214+
requests.add(methodEntryRequest);
215+
if (!future.isDone()) {
216+
this.putProperty("verified", true);
217+
future.complete(this);
218+
}
219+
}
220+
}
221+
return future;
222+
}
223+
224+
private Optional<MethodEntryRequest> createMethodEntryRequest(ReferenceType type) {
225+
return type.methodsByName(functionName).stream().findFirst().map(method -> {
226+
MethodEntryRequest request = vm.eventRequestManager().createMethodEntryRequest();
227+
228+
request.addClassFilter(type);
229+
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
230+
if (hitCount > 0) {
231+
request.addCountFilter(hitCount);
232+
}
233+
request.enable();
234+
return request;
235+
});
236+
}
237+
238+
@Override
239+
public Object getProperty(Object key) {
240+
return propertyMap.get(key);
241+
}
242+
243+
@Override
244+
public void putProperty(Object key, Object value) {
245+
propertyMap.put(key, value);
246+
}
247+
248+
@Override
249+
public String methodName() {
250+
return functionName;
251+
}
252+
253+
@Override
254+
public String className() {
255+
return className;
256+
}
257+
258+
}

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import com.microsoft.java.debug.core.Configuration;
2727
import com.microsoft.java.debug.core.IBreakpoint;
28+
import com.microsoft.java.debug.core.IMethodBreakpoint;
2829
import com.microsoft.java.debug.core.IWatchpoint;
2930

3031
public class BreakpointManager implements IBreakpointManager {
@@ -35,6 +36,7 @@ public class BreakpointManager implements IBreakpointManager {
3536
private List<IBreakpoint> breakpoints;
3637
private Map<String, HashMap<String, IBreakpoint>> sourceToBreakpoints;
3738
private Map<String, IWatchpoint> watchpoints;
39+
private Map<String, IMethodBreakpoint> methodBreakpoints;
3840
private AtomicInteger nextBreakpointId = new AtomicInteger(1);
3941

4042
/**
@@ -44,6 +46,7 @@ public BreakpointManager() {
4446
this.breakpoints = Collections.synchronizedList(new ArrayList<>(5));
4547
this.sourceToBreakpoints = new HashMap<>();
4648
this.watchpoints = new HashMap<>();
49+
this.methodBreakpoints = new HashMap<>();
4750
}
4851

4952
@Override
@@ -208,4 +211,61 @@ private String getWatchpointKey(IWatchpoint watchpoint) {
208211
public IWatchpoint[] getWatchpoints() {
209212
return this.watchpoints.values().stream().filter(wp -> wp != null).toArray(IWatchpoint[]::new);
210213
}
214+
215+
@Override
216+
public IMethodBreakpoint[] getMethodBreakpoints() {
217+
return this.methodBreakpoints.values().stream().filter(Objects::nonNull).toArray(IMethodBreakpoint[]::new);
218+
}
219+
220+
@Override
221+
public IMethodBreakpoint[] setMethodBreakpoints(IMethodBreakpoint[] breakpoints) {
222+
List<IMethodBreakpoint> result = new ArrayList<>();
223+
List<IMethodBreakpoint> toAdds = new ArrayList<>();
224+
List<IMethodBreakpoint> toRemoves = new ArrayList<>();
225+
226+
Set<String> visitedKeys = new HashSet<>();
227+
for (IMethodBreakpoint change : breakpoints) {
228+
if (change == null) {
229+
result.add(change);
230+
continue;
231+
}
232+
233+
String key = getMethodBreakpointKey(change);
234+
IMethodBreakpoint cache = methodBreakpoints.get(key);
235+
if (cache != null) {
236+
visitedKeys.add(key);
237+
result.add(cache);
238+
} else {
239+
toAdds.add(change);
240+
result.add(change);
241+
}
242+
}
243+
244+
for (IMethodBreakpoint cache : methodBreakpoints.values()) {
245+
if (!visitedKeys.contains(getMethodBreakpointKey(cache))) {
246+
toRemoves.add(cache);
247+
}
248+
}
249+
250+
for (IMethodBreakpoint toRemove : toRemoves) {
251+
try {
252+
// Destroy the method breakpoint on the debugee VM.
253+
toRemove.close();
254+
this.methodBreakpoints.remove(getMethodBreakpointKey(toRemove));
255+
} catch (Exception e) {
256+
logger.log(Level.SEVERE, String.format("Remove the method breakpoint exception: %s", e.toString()), e);
257+
}
258+
}
259+
260+
for (IMethodBreakpoint toAdd : toAdds) {
261+
toAdd.putProperty("id", this.nextBreakpointId.getAndIncrement());
262+
this.methodBreakpoints.put(getMethodBreakpointKey(toAdd), toAdd);
263+
}
264+
265+
return result.toArray(new IMethodBreakpoint[0]);
266+
}
267+
268+
private String getMethodBreakpointKey(IMethodBreakpoint breakpoint) {
269+
return breakpoint.className() + "#" + breakpoint.methodName();
270+
}
211271
}

0 commit comments

Comments
 (0)