Skip to content

Commit 0b3da27

Browse files
authored
PR2 of conditional BP (#154)
* Add basic impl of conditional BP * remove an empty line. * change a better var name * add cache for conditional breakpoint to avoid duplicate compilation * 1. fix an issue when exception is thrown during evaluation, the program is stopped at the exception 2. update a comment * 1. fix the issue findFirst will throw exception if none is found. 2. fix the issue of a dirty evaluation environment cause the next evaluation failure. * user filter to filter breakpoint event only * rollback unfinished changes. * 1. make ensureDebugTarget void and use exception for the error handling 2. keep only one CompletableFuture in evaluate
1 parent 075fbff commit 0b3da27

7 files changed

Lines changed: 180 additions & 76 deletions

File tree

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ public void setHitCount(int hitCount) {
127127
});
128128
}
129129

130+
@Override
131+
public void setCondition(String condition) {
132+
this.condition = condition;
133+
}
134+
130135
@Override
131136
public CompletableFuture<IBreakpoint> install() {
132137
// It's possible that different class loaders create new class with the same name.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ public interface IBreakpoint extends IDebugResource {
2929
Object getProperty(Object key);
3030

3131
String getCondition();
32+
33+
void setCondition(String condition);
3234
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public class BreakpointManager {
3131
private List<IBreakpoint> breakpoints;
3232
private HashMap<String, HashMap<String, IBreakpoint>> sourceToBreakpoints;
3333
private AtomicInteger nextBreakpointId = new AtomicInteger(1);
34+
// BreakpointManager is the owner class of the breakpoint to compiled expression map, it will remove
35+
// the breakpoint from this map if the breakpoint is removed or its condition is changed
36+
private Map<IBreakpoint, Object> breakpointExpressionMap = new HashMap<>();
3437

3538
/**
3639
* Constructor.
@@ -77,6 +80,7 @@ public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, bo
7780
} catch (Exception e) {
7881
logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e);
7982
}
83+
breakpointExpressionMap.remove(bp);
8084
this.breakpoints.remove(bp);
8185
}
8286
this.sourceToBreakpoints.put(source, null);
@@ -143,6 +147,7 @@ private void removeBreakpointsInternally(String source, IBreakpoint[] breakpoint
143147
// Destroy the breakpoint on the debugee VM.
144148
breakpoint.close();
145149
this.breakpoints.remove(breakpoint);
150+
breakpointExpressionMap.remove(breakpoint);
146151
breakpointMap.remove(String.valueOf(breakpoint.getLineNumber()));
147152
} catch (Exception e) {
148153
logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e);
@@ -166,6 +171,28 @@ public IBreakpoint[] getBreakpoints(String source) {
166171
return breakpointMap.values().toArray(new IBreakpoint[0]);
167172
}
168173

174+
175+
/**
176+
* Get the compiled expression map with breakpoint, it will be used in JdtEvaluationProvider#evaluateForBreakpoint for storing
177+
* the compiled expression when the first time this conditional breakpoint is hit.
178+
*
179+
* @return the compiled expression map
180+
*/
181+
public Map<IBreakpoint, Object> getBreakpointExpressionMap() {
182+
return breakpointExpressionMap;
183+
}
184+
185+
/**
186+
* Update the condition for the specified breakpoint, and clear the compiled expression for the breakpoint.
187+
*
188+
* @param breakpoint the conditional breakpoint
189+
* @param newCondition the new condition to be used.
190+
*/
191+
public void updateConditionCompiledExpression(IBreakpoint breakpoint, String newCondition) {
192+
breakpoint.setCondition(newCondition);
193+
breakpointExpressionMap.remove(breakpoint);
194+
}
195+
169196
/**
170197
* Cleanup all breakpoints and reset the breakpoint id counter.
171198
*/

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

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

14+
import java.util.Map;
1415
import java.util.concurrent.CompletableFuture;
1516

17+
import com.microsoft.java.debug.core.IBreakpoint;
1618
import com.sun.jdi.ThreadReference;
1719
import com.sun.jdi.Value;
1820

@@ -30,7 +32,7 @@ public interface IEvaluationProvider extends IProvider {
3032
boolean isInEvaluation(ThreadReference thread);
3133

3234
/**
33-
* Evaluate the expression at the given project and thread and stack frame depth, the promise is to be resolved/rejected when
35+
* Evaluate the expression at the given thread and stack frame depth, return the promise which is to be resolved/rejected when
3436
* the evaluation finishes.
3537
*
3638
* @param expression The expression to be evaluated
@@ -40,6 +42,19 @@ public interface IEvaluationProvider extends IProvider {
4042
*/
4143
CompletableFuture<Value> evaluate(String expression, ThreadReference thread, int depth);
4244

45+
/**
46+
* Evaluate the conditional breakpoint at the given thread and return the promise which is to be resolved/rejected when
47+
* the evaluation finishes. The breakpointExpressionMap value should be managed by this IEvaluationProvider, avoid duplicate compilation
48+
* on the same query when the conditional breakpoint is set inside a large loop, when the breakpoint is removed or the condition is changed,
49+
* the external owner of breakpointExpressionMap must remove the related map entry.
50+
*
51+
* @param breakpoint The conditional breakpoint
52+
* @param thread The jdi thread to the expression will be executed at
53+
* @param breakpointExpressionMap The map has breakpoint as the key and the compiled expression object for next evaluation use.
54+
* @return the evaluation result future
55+
*/
56+
CompletableFuture<Value> evaluateForBreakpoint(IBreakpoint breakpoint, ThreadReference thread, Map<IBreakpoint, Object> breakpointExpressionMap);
57+
4358

4459
/**
4560
* Call this method when the thread is to be resumed by user, it will first cancel ongoing evaluation tasks on specified thread and
@@ -48,5 +63,4 @@ public interface IEvaluationProvider extends IProvider {
4863
* @param thread the JDI thread reference where the evaluation task is executing at
4964
*/
5065
void clearState(ThreadReference thread);
51-
5266
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.microsoft.java.debug.core.adapter.ErrorCode;
2424
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
2525
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
26+
import com.microsoft.java.debug.core.adapter.IEvaluationProvider;
2627
import com.microsoft.java.debug.core.protocol.Events;
2728
import com.microsoft.java.debug.core.protocol.Messages.Response;
2829
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
@@ -94,6 +95,11 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
9495
// ignore since SetBreakpointsRequestHandler has already handled
9596
} else if (event instanceof ExceptionEvent) {
9697
ThreadReference thread = ((ExceptionEvent) event).thread();
98+
ThreadReference bpThread = ((BreakpointEvent) event).thread();
99+
IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class);
100+
if (engine.isInEvaluation(bpThread)) {
101+
return;
102+
}
97103
context.getProtocolServer().sendEvent(new Events.StoppedEvent("exception", thread.uniqueID()));
98104
debugEvent.shouldResume = false;
99105
} else {

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

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import java.util.Arrays;
1616
import java.util.List;
1717
import java.util.concurrent.CompletableFuture;
18-
import java.util.concurrent.ExecutionException;
1918
import java.util.logging.Level;
2019
import java.util.logging.Logger;
2120

@@ -42,9 +41,11 @@
4241
import com.microsoft.java.debug.core.protocol.Requests.SetBreakpointArguments;
4342
import com.microsoft.java.debug.core.protocol.Responses;
4443
import com.microsoft.java.debug.core.protocol.Types;
45-
import com.sun.jdi.PrimitiveValue;
44+
import com.sun.jdi.BooleanValue;
45+
import com.sun.jdi.Field;
46+
import com.sun.jdi.ObjectReference;
47+
import com.sun.jdi.ReferenceType;
4648
import com.sun.jdi.ThreadReference;
47-
import com.sun.jdi.Value;
4849
import com.sun.jdi.event.BreakpointEvent;
4950
import com.sun.jdi.event.Event;
5051
import com.sun.jdi.event.StepEvent;
@@ -131,9 +132,16 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
131132
Events.BreakpointEvent bpEvent = new Events.BreakpointEvent("new", this.convertDebuggerBreakpointToClient(bp, context));
132133
context.getProtocolServer().sendEvent(bpEvent);
133134
});
134-
} else if (toAdds[i].getHitCount() != added[i].getHitCount() && added[i].className() != null) {
135-
// Update hitCount condition.
136-
added[i].setHitCount(toAdds[i].getHitCount());
135+
} else if (added[i].className() != null) {
136+
if (toAdds[i].getHitCount() != added[i].getHitCount()) {
137+
// Update hitCount condition.
138+
added[i].setHitCount(toAdds[i].getHitCount());
139+
}
140+
141+
if (!StringUtils.equals(toAdds[i].getCondition(), added[i].getCondition())) {
142+
manager.updateConditionCompiledExpression(added[i], toAdds[i].getCondition());
143+
}
144+
137145
}
138146
res.add(this.convertDebuggerBreakpointToClient(added[i], context));
139147
}
@@ -149,10 +157,7 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
149157
private void registerBreakpointHandler(IDebugAdapterContext context) {
150158
IDebugSession debugSession = context.getDebugSession();
151159
if (debugSession != null) {
152-
debugSession.getEventHub().events().subscribe(debugEvent -> {
153-
if (!(debugEvent.event instanceof BreakpointEvent)) {
154-
return;
155-
}
160+
debugSession.getEventHub().events().filter(debugEvent -> debugEvent.event instanceof BreakpointEvent).subscribe(debugEvent -> {
156161
Event event = debugEvent.event;
157162
if (debugEvent.eventSet.size() > 1 && debugEvent.eventSet.stream().anyMatch(t -> t instanceof StepEvent)) {
158163
// The StepEvent and BreakpointEvent are grouped in the same event set only if they occurs at the same location and in the same thread.
@@ -167,23 +172,30 @@ private void registerBreakpointHandler(IDebugAdapterContext context) {
167172
// find the breakpoint related to this breakpoint event
168173
IBreakpoint conditionalBP = Arrays.asList(manager.getBreakpoints()).stream().filter(bp -> StringUtils.isNotBlank(bp.getCondition())
169174
&& bp.requests().contains(((BreakpointEvent) event).request())
170-
).findFirst().get();
175+
).findFirst().orElse(null);
171176
if (conditionalBP != null) {
172177
CompletableFuture.runAsync(() -> {
173-
Value value;
174-
try {
175-
value = engine.evaluate(conditionalBP.getCondition(), bpThread, 0).get();
176-
if (value instanceof PrimitiveValue) {
177-
boolean evaluationResultAsBool = ((PrimitiveValue) value).booleanValue();
178-
if (!evaluationResultAsBool) {
179-
debugEvent.eventSet.resume();
180-
return;
178+
engine.evaluateForBreakpoint(conditionalBP, bpThread, manager.getBreakpointExpressionMap()).whenComplete((value, ex) -> {
179+
// TODO, notify user when error is raised.
180+
boolean resume = false;
181+
if (value != null && ex == null) {
182+
if (value instanceof BooleanValue) {
183+
resume = !((BooleanValue) value).booleanValue();
184+
} else if (value instanceof ObjectReference
185+
&& ((ObjectReference) value).type().name().equals("java.lang.Boolean")) {
186+
// get boolean value from java.lang.Boolean object
187+
Field field = ((ReferenceType) ((ObjectReference) value).type()).fieldByName("value");
188+
resume = !((BooleanValue) ((ObjectReference) value).getValue(field)).booleanValue();
181189
}
182190
}
183-
} catch (InterruptedException | ExecutionException e) {
184-
// TODO: notify user about evaluation failure
185-
}
186-
context.getProtocolServer().sendEvent(new Events.StoppedEvent("breakpoint", bpThread.uniqueID()));
191+
if (resume) {
192+
debugEvent.eventSet.resume();
193+
// since the evaluation result is false, clear the evaluation environment caused by above evaluation.
194+
engine.clearState(bpThread);
195+
} else {
196+
context.getProtocolServer().sendEvent(new Events.StoppedEvent("breakpoint", bpThread.uniqueID()));
197+
}
198+
});
187199

188200
});
189201
} else {

0 commit comments

Comments
 (0)