Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ public void setHitCount(int hitCount) {
});
}

@Override
public void setCondition(String condition) {
this.condition = condition;
}

@Override
public CompletableFuture<IBreakpoint> install() {
// It's possible that different class loaders create new class with the same name.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ public interface IBreakpoint extends IDebugResource {
Object getProperty(Object key);

String getCondition();

void setCondition(String condition);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public class BreakpointManager {
private List<IBreakpoint> breakpoints;
private HashMap<String, HashMap<String, IBreakpoint>> sourceToBreakpoints;
private AtomicInteger nextBreakpointId = new AtomicInteger(1);
// BreakpointManager is the owner class of the breakpoint to compiled expression map, it will remove
// the breakpoint from this map if the breakpoint is removed or its condition is changed
private Map<IBreakpoint, Object> breakpointExpressionMap = new HashMap<>();

/**
* Constructor.
Expand Down Expand Up @@ -77,6 +80,7 @@ public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, bo
} catch (Exception e) {
logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e);
}
breakpointExpressionMap.remove(bp);
this.breakpoints.remove(bp);
}
this.sourceToBreakpoints.put(source, null);
Expand Down Expand Up @@ -143,6 +147,7 @@ private void removeBreakpointsInternally(String source, IBreakpoint[] breakpoint
// Destroy the breakpoint on the debugee VM.
breakpoint.close();
this.breakpoints.remove(breakpoint);
breakpointExpressionMap.remove(breakpoint);
breakpointMap.remove(String.valueOf(breakpoint.getLineNumber()));
} catch (Exception e) {
logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e);
Expand All @@ -166,6 +171,28 @@ public IBreakpoint[] getBreakpoints(String source) {
return breakpointMap.values().toArray(new IBreakpoint[0]);
}


/**
* Get the compiled expression map with breakpoint, it will be used in JdtEvaluationProvider#evaluateForBreakpoint for storing
* the compiled expression when the first time this conditional breakpoint is hit.
*
* @return the compiled expression map
*/
public Map<IBreakpoint, Object> getBreakpointExpressionMap() {
return breakpointExpressionMap;
}

/**
* Update the condition for the specified breakpoint, and clear the compiled expression for the breakpoint.
*
* @param breakpoint the conditional breakpoint
* @param newCondition the new condition to be used.
*/
public void updateConditionCompiledExpression(IBreakpoint breakpoint, String newCondition) {
breakpoint.setCondition(newCondition);
breakpointExpressionMap.remove(breakpoint);
}

/**
* Cleanup all breakpoints and reset the breakpoint id counter.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
package com.microsoft.java.debug.core.adapter;

public final class Constants {
public static final String PROJECTNAME = "projectName";
public static final String PROJECT_NAME = "projectName";
public static final String DEBUGGEE_ENCODING = "debuggeeEncoding";
public static final String MAIN_CLASS = "mainClass";
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@

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

import java.util.Map;
import java.util.concurrent.CompletableFuture;

import com.microsoft.java.debug.core.IBreakpoint;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;

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

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

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


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

}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
Map<String, Object> options = new HashMap<>();
options.put(Constants.DEBUGGEE_ENCODING, context.getDebuggeeEncoding());
if (attachArguments.projectName != null) {
options.put(Constants.PROJECTNAME, attachArguments.projectName);
options.put(Constants.PROJECT_NAME, attachArguments.projectName);
}
ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class);
sourceProvider.initialize(context, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.microsoft.java.debug.core.adapter.ErrorCode;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
import com.microsoft.java.debug.core.adapter.IEvaluationProvider;
import com.microsoft.java.debug.core.protocol.Events;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
Expand Down Expand Up @@ -94,6 +95,11 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
// ignore since SetBreakpointsRequestHandler has already handled
} else if (event instanceof ExceptionEvent) {
ThreadReference thread = ((ExceptionEvent) event).thread();
ThreadReference bpThread = ((BreakpointEvent) event).thread();
IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class);
if (engine.isInEvaluation(bpThread)) {
return;
}
context.getProtocolServer().sendEvent(new Events.StoppedEvent("exception", thread.uniqueID()));
debugEvent.shouldResume = false;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,16 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,

return launch(launchArguments, response, context).thenCompose(res -> {
if (res.success) {
ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class);

Map<String, Object> options = new HashMap<>();
options.put(Constants.DEBUGGEE_ENCODING, context.getDebuggeeEncoding());
if (launchArguments.projectName != null) {
options.put(Constants.PROJECTNAME, launchArguments.projectName);
options.put(Constants.PROJECT_NAME, launchArguments.projectName);
}
if (launchArguments.mainClass != null) {
options.put(Constants.MAIN_CLASS, launchArguments.mainClass);
}
ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class);
sourceProvider.initialize(context, options);
IEvaluationProvider evaluationProvider = context.getProvider(IEvaluationProvider.class);
evaluationProvider.initialize(context, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -42,9 +41,11 @@
import com.microsoft.java.debug.core.protocol.Requests.SetBreakpointArguments;
import com.microsoft.java.debug.core.protocol.Responses;
import com.microsoft.java.debug.core.protocol.Types;
import com.sun.jdi.PrimitiveValue;
import com.sun.jdi.BooleanValue;
import com.sun.jdi.Field;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;
import com.sun.jdi.event.BreakpointEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.StepEvent;
Expand Down Expand Up @@ -131,9 +132,16 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
Events.BreakpointEvent bpEvent = new Events.BreakpointEvent("new", this.convertDebuggerBreakpointToClient(bp, context));
context.getProtocolServer().sendEvent(bpEvent);
});
} else if (toAdds[i].getHitCount() != added[i].getHitCount() && added[i].className() != null) {
// Update hitCount condition.
added[i].setHitCount(toAdds[i].getHitCount());
} else if (added[i].className() != null) {
if (toAdds[i].getHitCount() != added[i].getHitCount()) {
// Update hitCount condition.
added[i].setHitCount(toAdds[i].getHitCount());
}

if (!StringUtils.equals(toAdds[i].getCondition(), added[i].getCondition())) {
manager.updateConditionCompiledExpression(added[i], toAdds[i].getCondition());
}

}
res.add(this.convertDebuggerBreakpointToClient(added[i], context));
}
Expand All @@ -149,10 +157,7 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
private void registerBreakpointHandler(IDebugAdapterContext context) {
IDebugSession debugSession = context.getDebugSession();
if (debugSession != null) {
debugSession.getEventHub().events().subscribe(debugEvent -> {
if (!(debugEvent.event instanceof BreakpointEvent)) {
return;
}
debugSession.getEventHub().events().filter(debugEvent -> debugEvent.event instanceof BreakpointEvent).subscribe(debugEvent -> {
Event event = debugEvent.event;
if (debugEvent.eventSet.size() > 1 && debugEvent.eventSet.stream().anyMatch(t -> t instanceof StepEvent)) {
// The StepEvent and BreakpointEvent are grouped in the same event set only if they occurs at the same location and in the same thread.
Expand All @@ -167,23 +172,42 @@ private void registerBreakpointHandler(IDebugAdapterContext context) {
// find the breakpoint related to this breakpoint event
IBreakpoint conditionalBP = Arrays.asList(manager.getBreakpoints()).stream().filter(bp -> StringUtils.isNotBlank(bp.getCondition())
&& bp.requests().contains(((BreakpointEvent) event).request())
).findFirst().get();
).findFirst().orElse(null);
if (conditionalBP != null) {
CompletableFuture.runAsync(() -> {
Value value;
try {
value = engine.evaluate(conditionalBP.getCondition(), bpThread, 0).get();
if (value instanceof PrimitiveValue) {
boolean evaluationResultAsBool = ((PrimitiveValue) value).booleanValue();
if (!evaluationResultAsBool) {
debugEvent.eventSet.resume();
return;
engine.evaluateForBreakpoint(conditionalBP, bpThread, manager.getBreakpointExpressionMap()).whenComplete((value, ex) -> {
boolean resume = false;
boolean resultNotBoolean = false;
if (value != null && ex == null) {
if (value instanceof BooleanValue) {
resume = !((BooleanValue) value).booleanValue();
} else if (value instanceof ObjectReference
&& ((ObjectReference) value).type().name().equals("java.lang.Boolean")) {
// get boolean value from java.lang.Boolean object
Field field = ((ReferenceType) ((ObjectReference) value).type()).fieldByName("value");
resume = !((BooleanValue) ((ObjectReference) value).getValue(field)).booleanValue();
} else {
resultNotBoolean = true;
}
}
if (resume) {
debugEvent.eventSet.resume();
// since the evaluation result is false, clear the evaluation environment caused by above evaluation.
engine.clearState(bpThread);
} else {
context.getProtocolServer().sendEvent(new Events.StoppedEvent("breakpoint", bpThread.uniqueID()));
if (ex != null) {
context.getProtocolServer().sendEvent(new Events.UserNotificationEvent(
Events.UserNotificationEvent.NotificationType.ERROR,
String.format("Breakpoint condition '%s' error: %s", conditionalBP.getCondition(), ex.getMessage())));
} else if (value == null || resultNotBoolean) {
context.getProtocolServer().sendEvent(new Events.UserNotificationEvent(
Events.UserNotificationEvent.NotificationType.WARNING,
String.format("Result of breakpoint condition '%s' is not a boolean, please correct your expression.",
conditionalBP.getCondition())));
}
}
} catch (InterruptedException | ExecutionException e) {
// TODO: notify user about evaluation failure
}
context.getProtocolServer().sendEvent(new Events.StoppedEvent("breakpoint", bpThread.uniqueID()));
});

});
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public StoppedEvent(String reason, long threadId) {
super("stopped");
this.reason = reason;
this.threadId = threadId;
this.allThreadsStopped = false;
allThreadsStopped = false;
}

/**
Expand Down Expand Up @@ -98,7 +98,7 @@ public static class ExitedEvent extends DebugEvent {

public ExitedEvent(int code) {
super("exited");
this.exitCode = code;
exitCode = code;
}
}

Expand Down Expand Up @@ -194,4 +194,22 @@ public HotCodeReplaceEvent(ChangeType changeType, String message) {
this.message = message;
}
}

public static class UserNotificationEvent extends DebugEvent {
public enum NotificationType {
ERROR, WARNING, INFORMATION
}

public NotificationType notificationType;
public String message;

/**
* Constructor.
*/
public UserNotificationEvent(NotificationType notifyType, String message) {
super("usernotification");
this.notificationType = notifyType;
this.message = message;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public String getSourceFileURI(String fullyQualifiedName, String sourcePath) {

private synchronized ISourceContainer[] getSourceContainers() {
if (sourceContainers == null) {
sourceContainers = JdtUtils.getSourceContainers((String) options.get(Constants.PROJECTNAME));
sourceContainers = JdtUtils.getSourceContainers((String) options.get(Constants.PROJECT_NAME));
}

return sourceContainers;
Expand Down
Loading