diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java index 82859b268..c10bf18f2 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java @@ -41,6 +41,7 @@ public class Breakpoint implements IBreakpoint { private int hitCount = 0; private String condition = null; private String logMessage = null; + private int suspendPolicy = BreakpointRequest.SUSPEND_EVENT_THREAD; private HashMap propertyMap = new HashMap<>(); private boolean async = false; @@ -412,7 +413,7 @@ private CompletableFuture> createBreakpointRequests(List newLocations.forEach(location -> { BreakpointRequest request = vm.eventRequestManager().createBreakpointRequest(location); - request.setSuspendPolicy(BreakpointRequest.SUSPEND_EVENT_THREAD); + request.setSuspendPolicy(suspendPolicy); if (hitCount > 0) { request.addCountFilter(hitCount); } @@ -468,4 +469,14 @@ public void putProperty(Object key, Object value) { public Object getProperty(Object key) { return propertyMap.get(key); } + + @Override + public int getSuspendPolicy() { + return this.suspendPolicy; + } + + @Override + public void setSuspendPolicy(int suspendPolicy) { + this.suspendPolicy = suspendPolicy; + } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java index 38a234fa9..01edaf2f3 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java @@ -127,13 +127,17 @@ public void terminate() { } @Override - public IBreakpoint createBreakpoint(JavaBreakpointLocation sourceLocation, int hitCount, String condition, String logMessage) { - return new EvaluatableBreakpoint(vm, this.getEventHub(), sourceLocation, hitCount, condition, logMessage); + public IBreakpoint createBreakpoint(JavaBreakpointLocation sourceLocation, int hitCount, String condition, String logMessage, int suspendPolicy) { + EvaluatableBreakpoint breakpoint = new EvaluatableBreakpoint(vm, this.getEventHub(), sourceLocation, hitCount, condition, logMessage); + breakpoint.setSuspendPolicy(suspendPolicy); + return breakpoint; } @Override - public IBreakpoint createBreakpoint(String className, int lineNumber, int hitCount, String condition, String logMessage) { - return new EvaluatableBreakpoint(vm, this.getEventHub(), className, lineNumber, hitCount, condition, logMessage); + public IBreakpoint createBreakpoint(String className, int lineNumber, int hitCount, String condition, String logMessage, int suspendPolicy) { + EvaluatableBreakpoint breakpoint = new EvaluatableBreakpoint(vm, this.getEventHub(), className, lineNumber, hitCount, condition, logMessage); + breakpoint.setSuspendPolicy(suspendPolicy); + return breakpoint; } @Override @@ -142,18 +146,21 @@ public IWatchpoint createWatchPoint(String className, String fieldName, String a } @Override - public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught) { - setExceptionBreakpoints(notifyCaught, notifyUncaught, null, null); + public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, int suspendModeOnCaught, int suspendModeOnUncaught) { + setExceptionBreakpoints(notifyCaught, notifyUncaught, suspendModeOnCaught, suspendModeOnUncaught, null, null); } @Override - public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters) { - setExceptionBreakpoints(notifyCaught, notifyUncaught, null, classFilters, classExclusionFilters); + public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, int suspendModeOnCaught, + int suspendModeOnUncaught, String[] classFilters, String[] classExclusionFilters) { + setExceptionBreakpoints(notifyCaught, notifyUncaught, suspendModeOnCaught, suspendModeOnUncaught, null, + classFilters, classExclusionFilters); } @Override - public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] exceptionTypes, - String[] classFilters, String[] classExclusionFilters) { + public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, int suspendModeOnCaught, + int suspendModeOnUncaught, String[] exceptionTypes, + String[] classFilters, String[] classExclusionFilters) { EventRequestManager manager = vm.eventRequestManager(); try { @@ -169,7 +176,8 @@ public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught subscriptions.clear(); eventRequests.clear(); - // When no exception breakpoints are requested, no need to create an empty exception request. + // When no exception breakpoints are requested, no need to create an empty + // exception request. if (notifyCaught || notifyUncaught) { // from: https://www.javatips.net/api/REPLmode-master/src/jm/mode/replmode/REPLRunner.java // Calling this seems to set something internally to make the @@ -184,19 +192,8 @@ public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught // See org.eclipse.debug.jdi.tests.AbstractJDITest for the example. if (exceptionTypes == null || exceptionTypes.length == 0) { - ExceptionRequest request = manager.createExceptionRequest(null, notifyCaught, notifyUncaught); - request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); - if (classFilters != null) { - for (String classFilter : classFilters) { - request.addClassFilter(classFilter); - } - } - if (classExclusionFilters != null) { - for (String exclusionFilter : classExclusionFilters) { - request.addClassExclusionFilter(exclusionFilter); - } - } - request.enable(); + createExceptionBreakpoint(null, notifyCaught, notifyUncaught, suspendModeOnCaught, + suspendModeOnUncaught, classFilters, classExclusionFilters); return; } @@ -212,31 +209,36 @@ public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught eventRequests.add(classPrepareRequest); Disposable subscription = eventHub.events() - .filter(debugEvent -> debugEvent.event instanceof ClassPrepareEvent - && eventRequests.contains(debugEvent.event.request())) - .subscribe(debugEvent -> { - ClassPrepareEvent event = (ClassPrepareEvent) debugEvent.event; - createExceptionBreakpoint(event.referenceType(), notifyCaught, notifyUncaught, classFilters, classExclusionFilters); - }); + .filter(debugEvent -> debugEvent.event instanceof ClassPrepareEvent + && eventRequests.contains(debugEvent.event.request())) + .subscribe(debugEvent -> { + ClassPrepareEvent event = (ClassPrepareEvent) debugEvent.event; + createExceptionBreakpoint(event.referenceType(), notifyCaught, notifyUncaught, + suspendModeOnCaught, suspendModeOnUncaught, classFilters, classExclusionFilters); + }); subscriptions.add(subscription); // register exception breakpoint in the loaded classes. for (ReferenceType refType : vm.classesByName(exceptionType)) { - createExceptionBreakpoint(refType, notifyCaught, notifyUncaught, classFilters, classExclusionFilters); + createExceptionBreakpoint(refType, notifyCaught, notifyUncaught, suspendModeOnCaught, + suspendModeOnUncaught, classFilters, classExclusionFilters); } } } } @Override - public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] exceptionTypes, + public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, int suspendModeOnCaught, + int suspendModeOnUncaught, String[] exceptionTypes, String[] classFilters, String[] classExclusionFilters, boolean async) { if (async) { AsyncJdwpUtils.runAsync(() -> { - setExceptionBreakpoints(notifyCaught, notifyUncaught, exceptionTypes, classFilters, classExclusionFilters); + setExceptionBreakpoints(notifyCaught, notifyUncaught, suspendModeOnCaught, suspendModeOnUncaught, + exceptionTypes, classFilters, classExclusionFilters); }); } else { - setExceptionBreakpoints(notifyCaught, notifyUncaught, exceptionTypes, classFilters, classExclusionFilters); + setExceptionBreakpoints(notifyCaught, notifyUncaught, suspendModeOnCaught, suspendModeOnUncaught, + exceptionTypes, classFilters, classExclusionFilters); } } @@ -267,10 +269,20 @@ public IMethodBreakpoint createFunctionBreakpoint(String className, String funct } private void createExceptionBreakpoint(ReferenceType refType, boolean notifyCaught, boolean notifyUncaught, - String[] classFilters, String[] classExclusionFilters) { + int suspendModeOnCaught, int suspendModeOnUncaught, String[] classFilters, String[] classExclusionFilters) { + if (suspendModeOnCaught == suspendModeOnUncaught) { + createExceptionBreakpoint(refType, notifyCaught, notifyUncaught, suspendModeOnCaught, classFilters, classExclusionFilters); + } else { + createExceptionBreakpoint(refType, notifyCaught, notifyUncaught, suspendModeOnCaught, classFilters, classExclusionFilters); + createExceptionBreakpoint(refType, notifyCaught, notifyUncaught, suspendModeOnUncaught, classFilters, classExclusionFilters); + } + } + + private void createExceptionBreakpoint(ReferenceType refType, boolean notifyCaught, boolean notifyUncaught, + int suspendMode, String[] classFilters, String[] classExclusionFilters) { EventRequestManager manager = vm.eventRequestManager(); ExceptionRequest request = manager.createExceptionRequest(refType, notifyCaught, notifyUncaught); - request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + request.setSuspendPolicy(suspendMode); if (classFilters != null) { for (String classFilter : classFilters) { request.addClassFilter(classFilter); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IBreakpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IBreakpoint.java index 40995e9dd..a8d6255d1 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IBreakpoint.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IBreakpoint.java @@ -49,6 +49,10 @@ public interface IBreakpoint extends IDebugResource { void setLogMessage(String logMessage); + int getSuspendPolicy(); + + void setSuspendPolicy(int suspendPolicy); + default void setAsync(boolean async) { } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java index 6cc3f3a46..fdfd208b4 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java @@ -28,20 +28,25 @@ public interface IDebugSession { void terminate(); // breakpoints - IBreakpoint createBreakpoint(String className, int lineNumber, int hitCount, String condition, String logMessage); + IBreakpoint createBreakpoint(String className, int lineNumber, int hitCount, String condition, String logMessage, + int suspendPolicy); - IBreakpoint createBreakpoint(JavaBreakpointLocation sourceLocation, int hitCount, String condition, String logMessage); + IBreakpoint createBreakpoint(JavaBreakpointLocation sourceLocation, int hitCount, String condition, + String logMessage, int suspendPolicy); IWatchpoint createWatchPoint(String className, String fieldName, String accessType, String condition, int hitCount); - void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught); + void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, int suspendModeOnCaught, + int suspendModeOnUncaught); - void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters); + void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, int suspendModeOnCaught, + int suspendModeOnUncaught, String[] classFilters, String[] classExclusionFilters); - void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] exceptionTypes, String[] classFilters, String[] classExclusionFilters); + void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, int suspendModeOnCaught, + int suspendModeOnUncaught, String[] exceptionTypes, String[] classFilters, String[] classExclusionFilters); - void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] exceptionTypes, String[] classFilters, String[] classExclusionFilters, - boolean async); + void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, int suspendModeOnCaught, + int suspendModeOnUncaught, String[] exceptionTypes, String[] classFilters, String[] classExclusionFilters, boolean async); IMethodBreakpoint createFunctionBreakpoint(String className, String functionName, String condition, int hitCount); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java index c30aa8742..a2ac405ad 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java @@ -34,6 +34,7 @@ import com.microsoft.java.debug.core.DebugException; import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.sun.jdi.request.EventRequest; import com.microsoft.java.debug.core.protocol.Responses; import com.microsoft.java.debug.core.protocol.Types; @@ -364,4 +365,13 @@ public static int[] binarySearchMappedLines(int[] lineMappings, int targetLine) return values; } + + public static int suspendPolicyFromBreakpointMode(String breakpointMode) { + if (Constants.SUSPEND_VM.equals(breakpointMode)) { + return EventRequest.SUSPEND_ALL; + } else if (Constants.SUSPEND_THREAD.equals(breakpointMode)) { + return EventRequest.SUSPEND_EVENT_THREAD; + } + return EventRequest.SUSPEND_EVENT_THREAD; + } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Constants.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Constants.java index 2e523aba2..1448bd51a 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Constants.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Constants.java @@ -15,4 +15,8 @@ public final class Constants { public static final String PROJECT_NAME = "projectName"; public static final String DEBUGGEE_ENCODING = "debuggeeEncoding"; public static final String MAIN_CLASS = "mainClass"; + + // Breakpoint suspend modes + public static final String SUSPEND_VM = "suspendVM"; + public static final String SUSPEND_THREAD = "suspendThread"; } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java index 1c543bce4..5cd93e15b 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java @@ -41,6 +41,7 @@ import com.sun.jdi.event.VMDeathEvent; import com.sun.jdi.event.VMDisconnectEvent; import com.sun.jdi.event.VMStartEvent; +import com.sun.jdi.request.EventRequest; public class ConfigurationDoneRequestHandler implements IDebugRequestHandler { protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); @@ -119,7 +120,8 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, ((ExceptionEvent) event).catchLocation() == null); context.getExceptionManager().setException(thread.uniqueID(), jdiException); context.getThreadCache().addEventThread(thread, "exception"); - context.getProtocolServer().sendEvent(new Events.StoppedEvent("exception", thread.uniqueID())); + boolean suspendAll = event.request() != null ? event.request().suspendPolicy() == EventRequest.SUSPEND_ALL : false; + context.getProtocolServer().sendEvent(new Events.StoppedEvent("exception", thread.uniqueID(), suspendAll)); debugEvent.shouldResume = false; } else { isImportantEvent = false; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java index 6b9245166..115b7f188 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.concurrent.CompletableFuture; +import com.microsoft.java.debug.core.adapter.Constants; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; import com.microsoft.java.debug.core.protocol.Messages; @@ -22,6 +23,7 @@ import com.microsoft.java.debug.core.protocol.Types; public class InitializeRequestHandler implements IDebugRequestHandler { + @Override public List getTargetCommands() { return Arrays.asList(Requests.Command.INITIALIZE); @@ -66,6 +68,30 @@ public CompletableFuture handle(Requests.Command command, Req caps.supportsClipboardContext = true; caps.supportsBreakpointLocationsRequest = true; caps.supportsStepInTargetsRequest = true; + + // Add breakpoint modes for suspend behavior + Types.BreakpointMode[] breakpointModes = { + new Types.BreakpointMode( + Constants.SUSPEND_THREAD, + "Suspend Thread", + "Suspends only the thread that hit the breakpoint", + new Types.BreakpointModeApplicability[] { + Types.BreakpointModeApplicability.SOURCE + // data and function breakpoints are not supported by VS Code + // instruction breakpoints are not supported by this adapter + } + ), + new Types.BreakpointMode( + Constants.SUSPEND_VM, + "Suspend VM", + "Suspends the entire virtual machine when breakpoint is hit", + new Types.BreakpointModeApplicability[] { + Types.BreakpointModeApplicability.SOURCE + } + ) + }; + caps.breakpointModes = breakpointModes; + response.body = caps; context.setInitialized(true); return CompletableFuture.completedFuture(response); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java index 09dafd1b0..42c5e11fc 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java @@ -134,6 +134,19 @@ public CompletableFuture handle(Command command, Arguments arguments, added[i].setCondition(toAdds[i].getCondition()); } + if (toAdds[i].getSuspendPolicy() != added[i].getSuspendPolicy()) { + added[i].setSuspendPolicy(toAdds[i].getSuspendPolicy()); + try { + added[i].close(); + added[i].install().thenAccept(bp -> { + Events.BreakpointEvent bpEvent = new Events.BreakpointEvent("changed", this.convertDebuggerBreakpointToClient(bp, context)); + context.getProtocolServer().sendEvent(bpEvent); + }); + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Close breakpoint exception: %s", e.toString()), e); + } + } + } res.add(this.convertDebuggerBreakpointToClient(added[i], context)); } @@ -200,6 +213,7 @@ private void registerBreakpointHandler(IDebugAdapterContext context) { // find the breakpoint related to this breakpoint event IBreakpoint expressionBP = getAssociatedEvaluatableBreakpoint(context, (BreakpointEvent) event); String breakpointName = computeBreakpointName(event.request()); + boolean suspendAll = event.request() != null ? event.request().suspendPolicy() == EventRequest.SUSPEND_ALL : false; if (expressionBP != null) { CompletableFuture.runAsync(() -> { @@ -212,15 +226,14 @@ private void registerBreakpointHandler(IDebugAdapterContext context) { debugEvent.eventSet.resume(); } else { context.getThreadCache().addEventThread(bpThread, breakpointName); - context.getProtocolServer().sendEvent(new Events.StoppedEvent( - breakpointName, bpThread.uniqueID())); + context.getProtocolServer().sendEvent(new Events.StoppedEvent(breakpointName, bpThread.uniqueID(), suspendAll)); } }); }); } else { context.getThreadCache().addEventThread(bpThread, breakpointName); context.getProtocolServer().sendEvent(new Events.StoppedEvent( - breakpointName, bpThread.uniqueID())); + breakpointName, bpThread.uniqueID(), suspendAll)); } debugEvent.shouldResume = false; } @@ -335,7 +348,7 @@ private IBreakpoint[] convertClientBreakpointsToDebugger(String sourceFile, Type } } breakpoints[i] = context.getDebugSession().createBreakpoint(locations[i], hitCount, sourceBreakpoints[i].condition, - sourceBreakpoints[i].logMessage); + sourceBreakpoints[i].logMessage, AdapterUtils.suspendPolicyFromBreakpointMode(sourceBreakpoints[i].mode)); if (sourceProvider.supportsRealtimeBreakpointVerification() && StringUtils.isNotBlank(locations[i].className())) { breakpoints[i].putProperty("verified", true); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetExceptionBreakpointsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetExceptionBreakpointsRequestHandler.java index b51c5fe27..9bb77e88f 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetExceptionBreakpointsRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetExceptionBreakpointsRequestHandler.java @@ -14,9 +14,6 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; - -import org.apache.commons.lang3.ArrayUtils; - import com.microsoft.java.debug.core.DebugSettings; import com.microsoft.java.debug.core.IDebugSession; import com.microsoft.java.debug.core.DebugSettings.IDebugSettingChangeListener; @@ -29,9 +26,11 @@ import com.microsoft.java.debug.core.protocol.Requests.Command; import com.microsoft.java.debug.core.protocol.Requests.ExceptionFilters; import com.microsoft.java.debug.core.protocol.Requests.SetExceptionBreakpointsArguments; +import com.microsoft.java.debug.core.protocol.Types.ExceptionFilterOptions; import com.microsoft.java.debug.core.protocol.Types; import com.sun.jdi.event.VMDeathEvent; import com.sun.jdi.event.VMDisconnectEvent; +import com.sun.jdi.request.EventRequest; public class SetExceptionBreakpointsRequestHandler implements IDebugRequestHandler, IDebugSettingChangeListener { private IDebugSession debugSession = null; @@ -39,6 +38,8 @@ public class SetExceptionBreakpointsRequestHandler implements IDebugRequestHandl private boolean notifyCaught = false; private boolean notifyUncaught = false; private boolean asyncJDWP = false; + private int suspendModeOnCaught; + private int suspendModeOnUncaught; @Override public List getTargetCommands() { @@ -64,11 +65,37 @@ public synchronized CompletableFuture handle(Command command, Argument }); } - String[] filters = ((SetExceptionBreakpointsArguments) arguments).filters; + SetExceptionBreakpointsArguments requestArgs = (SetExceptionBreakpointsArguments) arguments; + String[] filters = requestArgs.filters; + try { - this.notifyCaught = ArrayUtils.contains(filters, Types.ExceptionBreakpointFilter.CAUGHT_EXCEPTION_FILTER_NAME); - this.notifyUncaught = ArrayUtils.contains(filters, Types.ExceptionBreakpointFilter.UNCAUGHT_EXCEPTION_FILTER_NAME); - setExceptionBreakpoints(context.getDebugSession(), this.notifyCaught, this.notifyUncaught); + this.notifyCaught = false; + this.notifyUncaught = false; + if (filters != null) { + for (String filter : filters) { + if (filter.equals(Types.ExceptionBreakpointFilter.CAUGHT_EXCEPTION_FILTER_NAME)) { + this.notifyCaught = true; + } else if (filter.equals(Types.ExceptionBreakpointFilter.UNCAUGHT_EXCEPTION_FILTER_NAME)) { + this.notifyUncaught = true; + } + } + } + this.suspendModeOnCaught = EventRequest.SUSPEND_EVENT_THREAD; + this.suspendModeOnUncaught = EventRequest.SUSPEND_EVENT_THREAD; + + ExceptionFilterOptions[] filterOptions = requestArgs.filterOptions; + if (filterOptions != null) { + for (ExceptionFilterOptions filterOption : requestArgs.filterOptions) { + if (filterOption.filterId.equals(Types.ExceptionBreakpointFilter.CAUGHT_EXCEPTION_FILTER_NAME)) { + this.notifyCaught = true; + this.suspendModeOnCaught = AdapterUtils.suspendPolicyFromBreakpointMode(filterOption.mode); + } else if (filterOption.filterId.equals(Types.ExceptionBreakpointFilter.UNCAUGHT_EXCEPTION_FILTER_NAME)) { + this.notifyUncaught = true; + this.suspendModeOnUncaught = AdapterUtils.suspendPolicyFromBreakpointMode(filterOption.mode); + } + } + } + setExceptionBreakpoints(context.getDebugSession(), this.notifyCaught, this.notifyUncaught, suspendModeOnCaught, suspendModeOnUncaught); return CompletableFuture.completedFuture(response); } catch (Exception ex) { throw AdapterUtils.createCompletionException( @@ -78,19 +105,21 @@ public synchronized CompletableFuture handle(Command command, Argument } } - private void setExceptionBreakpoints(IDebugSession debugSession, boolean notifyCaught, boolean notifyUncaught) { + private void setExceptionBreakpoints(IDebugSession debugSession, boolean notifyCaught, boolean notifyUncaught, + int suspendModeOnCaught, int suspendModeOnUncaught) { ExceptionFilters exceptionFilters = DebugSettings.getCurrent().exceptionFilters; String[] exceptionTypes = (exceptionFilters == null ? null : exceptionFilters.exceptionTypes); String[] classFilters = (exceptionFilters == null ? null : exceptionFilters.allowClasses); String[] classExclusionFilters = (exceptionFilters == null ? null : exceptionFilters.skipClasses); - debugSession.setExceptionBreakpoints(notifyCaught, notifyUncaught, exceptionTypes, classFilters, classExclusionFilters, this.asyncJDWP); + debugSession.setExceptionBreakpoints(notifyCaught, notifyUncaught, suspendModeOnCaught, suspendModeOnUncaught, + exceptionTypes, classFilters, classExclusionFilters, this.asyncJDWP); } @Override public synchronized void update(DebugSettings oldSettings, DebugSettings newSettings) { try { if (newSettings != null && newSettings.exceptionFiltersUpdated) { - setExceptionBreakpoints(debugSession, notifyCaught, notifyUncaught); + setExceptionBreakpoints(debugSession, notifyCaught, notifyUncaught, suspendModeOnCaught, suspendModeOnUncaught); } } catch (Exception ex) { DebugSettings.removeDebugSettingChangeListener(this); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java index 1129d230e..3223dc613 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java @@ -17,6 +17,8 @@ import com.google.gson.annotations.SerializedName; import com.microsoft.java.debug.core.protocol.Types.DataBreakpoint; +import com.microsoft.java.debug.core.protocol.Types.ExceptionFilterOptions; +import com.microsoft.java.debug.core.protocol.Types.ExceptionOptions; import com.microsoft.java.debug.core.protocol.Types.Source; /** @@ -250,6 +252,20 @@ public static class SetFunctionBreakpointsArguments extends Arguments { public static class SetExceptionBreakpointsArguments extends Arguments { public String[] filters = new String[0]; + /** + * Set of exception filters and their options. The set of all possible + * exception filters is defined by the `exceptionBreakpointFilters` + * capability. This attribute is only honored by a debug adapter if the + * corresponding capability `supportsExceptionFilterOptions` is true. The + * `filter` and `filterOptions` sets are additive. + */ + public ExceptionFilterOptions[] filterOptions; + + /** + * Configuration options for selected exceptions. The attribute is only honored by a debug adapter + * if the corresponding capability `supportsExceptionOptions` is true. + */ + public ExceptionOptions[] exceptionOptions; } public static class ExceptionInfoArguments extends Arguments { diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java index 33308af6d..6ffcb9521 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java @@ -209,6 +209,7 @@ public static class SourceBreakpoint { public String hitCondition; public String condition; public String logMessage; + public String mode; public SourceBreakpoint(int line, int column) { this.line = line; @@ -233,6 +234,14 @@ public SourceBreakpoint(int line, String condition, String hitCondition, int col this.condition = condition; this.hitCondition = hitCondition; } + + public SourceBreakpoint(int line, String condition, String hitCondition, int column, String mode) { + this.line = line; + this.column = column; + this.condition = condition; + this.hitCondition = hitCondition; + this.mode = mode; + } } public static class FunctionBreakpoint { @@ -414,6 +423,129 @@ public VariablePresentationHint(boolean lazy) { } } + /** + * An ExceptionPathSegment represents a segment in a path that is used to match leafs or nodes in a tree of exceptions. + * If a segment consists of more than one name, it matches the names provided if negate is false or missing, + * or it matches anything except the names provided if negate is true. + */ + public static class ExceptionPathSegment { + /** + * If false or missing this segment matches the names provided, otherwise it matches anything except the names provided. + */ + public boolean negate; + + /** + * Depending on the value of negate the names that should match or not match. + */ + public String[] names; + + public ExceptionPathSegment() { + } + + public ExceptionPathSegment(boolean negate, String[] names) { + this.negate = negate; + this.names = names; + } + } + + /** + * An ExceptionOptions assigns configuration options to a set of exceptions. + */ + public static class ExceptionOptions { + /** + * A path that selects a single or multiple exceptions in a tree. If path is missing, the whole tree is selected. + * By convention the first segment of the path is a category that is used to group exceptions in the UI. + */ + public ExceptionPathSegment[] path; + + /** + * Condition when a thrown exception should result in a break. + */ + public ExceptionBreakMode breakMode; + + public ExceptionOptions() { + } + + public ExceptionOptions(ExceptionPathSegment[] path, ExceptionBreakMode breakMode) { + this.path = path; + this.breakMode = breakMode; + } + } + + /** + * An ExceptionFilterOptions is used to specify an exception filter together with its options. + */ + public static class ExceptionFilterOptions { + /** + * ID of an exception filter returned by the exceptionBreakpointFilters capability. + */ + public String filterId; + + /** + * An expression for conditional exceptions. The exception breaks into the debugger if the result of the condition is true. + */ + public String condition; + + /** + * The mode of this exception breakpoint. If defined, this must be one of the breakpointModes the debug adapter advertised in its Capabilities. + */ + public String mode; + + public ExceptionFilterOptions() { + } + + public ExceptionFilterOptions(String filterId) { + this.filterId = filterId; + } + + public ExceptionFilterOptions(String filterId, String condition, String mode) { + this.filterId = filterId; + this.condition = condition; + this.mode = mode; + } + } + + public static enum BreakpointModeApplicability { + @SerializedName("source") + SOURCE, + @SerializedName("exception") + EXCEPTION, + @SerializedName("data") + DATA, + @SerializedName("instruction") + INSTRUCTION, + } + + public static class BreakpointMode { + public BreakpointMode(String mode, String label, String description, BreakpointModeApplicability[] appliesTo) { + this.mode = mode; + this.label = label; + this.description = description; + this.appliesTo = appliesTo; + } + /** + * The internal ID of the mode. This value is passed to the `setBreakpoints` + * request. + */ + public String mode; + + /** + * The name of the breakpoint mode. This is shown in the UI. + */ + public String label; + + /** + * A help text providing additional information about the breakpoint mode. + * This string is typically shown as a hover and can be translated. + */ + public String description; + + /** + * Describes one or more type of breakpoint this mode applies to. + */ + public BreakpointModeApplicability[] appliesTo; + } + public static class Capabilities { public boolean supportsConfigurationDoneRequest; public boolean supportsHitConditionalBreakpoints; @@ -434,6 +566,7 @@ public static class Capabilities { // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_BreakpointLocations public boolean supportsBreakpointLocationsRequest; public boolean supportsStepInTargetsRequest; + public BreakpointMode[] breakpointModes; } public static class StepInTarget { diff --git a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/AbstractJdiTestCase.java b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/AbstractJdiTestCase.java index 4d0a122a5..17d6bad95 100644 --- a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/AbstractJdiTestCase.java +++ b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/AbstractJdiTestCase.java @@ -22,6 +22,7 @@ import com.sun.jdi.Value; import com.sun.jdi.VirtualMachine; import com.sun.jdi.event.BreakpointEvent; +import com.sun.jdi.request.EventRequest; public abstract class AbstractJdiTestCase extends EasyMockSupport { private static int TEST_TIME_OUT = 1000 * 10; @@ -38,7 +39,7 @@ protected BreakpointEvent waitForBreakPointEvent(String breakpointAtClass, int l } IDebugSession debugSession = getCurrentDebugSession(); - IBreakpoint breakpointToAdd = debugSession.createBreakpoint(breakpointAtClass, line, 0, null, null); + IBreakpoint breakpointToAdd = debugSession.createBreakpoint(breakpointAtClass, line, 0, null, null, EventRequest.SUSPEND_EVENT_THREAD); breakpointToAdd.install().thenAccept(t -> { System.out.println("Breakpoint is accepted."); });