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..d8316c2e3 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 @@ -42,24 +42,28 @@ public class Breakpoint implements IBreakpoint { private String condition = null; private String logMessage = null; private HashMap propertyMap = new HashMap<>(); + private final boolean suspendAllThreads; private boolean async = false; - Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber) { - this(vm, eventHub, className, lineNumber, 0, null); + Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, boolean suspendAllThreads) { + this(vm, eventHub, className, lineNumber, 0, null, suspendAllThreads); } - Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount) { - this(vm, eventHub, className, lineNumber, hitCount, null); + Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, boolean suspendAllThreads) { + this(vm, eventHub, className, lineNumber, hitCount, null, suspendAllThreads); } - Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, String condition) { - this(vm, eventHub, className, lineNumber, hitCount, condition, null); + Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, + String condition, boolean suspendAllThreads) { + this(vm, eventHub, className, lineNumber, hitCount, condition, null, suspendAllThreads); } - Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, String condition, String logMessage) { + Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, + String condition, String logMessage, boolean suspendAllThreads) { this.vm = vm; this.eventHub = eventHub; + this.suspendAllThreads = suspendAllThreads; String contextClass = className; String methodName = null; String methodSignature = null; @@ -79,13 +83,15 @@ public class Breakpoint implements IBreakpoint { this.logMessage = logMessage; } - Breakpoint(VirtualMachine vm, IEventHub eventHub, JavaBreakpointLocation sourceLocation, int hitCount, String condition, String logMessage) { + Breakpoint(VirtualMachine vm, IEventHub eventHub, JavaBreakpointLocation sourceLocation, int hitCount, + String condition, String logMessage, boolean suspendAllThreads) { this.vm = vm; this.eventHub = eventHub; this.sourceLocation = sourceLocation; this.hitCount = hitCount; this.condition = condition; this.logMessage = logMessage; + this.suspendAllThreads = suspendAllThreads; } // IDebugResource @@ -203,6 +209,19 @@ public void setAsync(boolean async) { this.async = async; } + @Override + public void setSuspendPolicy(String policy) { + } + + @Override + public String getSuspendPolicy() { + return suspendAllThreads ? "SUSPEND_ALL" : "SUSPEND_EVENT_THREAD"; + } + + protected boolean suspendAllThreads() { + return suspendAllThreads; + } + @Override public CompletableFuture install() { // It's possible that different class loaders create new class with the same name. @@ -412,7 +431,11 @@ private CompletableFuture> createBreakpointRequests(List newLocations.forEach(location -> { BreakpointRequest request = vm.eventRequestManager().createBreakpointRequest(location); - request.setSuspendPolicy(BreakpointRequest.SUSPEND_EVENT_THREAD); + if ("SUSPEND_ALL".equals(getSuspendPolicy())) { + request.setSuspendPolicy(BreakpointRequest.SUSPEND_ALL); + } else { + request.setSuspendPolicy(BreakpointRequest.SUSPEND_EVENT_THREAD); + } if (hitCount > 0) { request.addCountFilter(hitCount); } 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..c040f5b79 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 @@ -36,9 +36,12 @@ public class DebugSession implements IDebugSession { private EventHub eventHub = new EventHub(); private List eventRequests = new ArrayList<>(); private List subscriptions = new ArrayList<>(); + private final boolean suspendAllThreads; public DebugSession(VirtualMachine virtualMachine) { vm = virtualMachine; + // Capture suspend policy at session start - this persists for the session lifetime + this.suspendAllThreads = DebugSettings.getCurrent().suspendAllThreads; } @Override @@ -114,7 +117,11 @@ public void resume() { @Override public void detach() { - vm.dispose(); + try { + vm.dispose(); + } catch (VMDisconnectedException ex) { + // ignore — VM already disconnected (e.g. process was terminated first) + } } @Override @@ -128,17 +135,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); + return new EvaluatableBreakpoint(vm, this.getEventHub(), sourceLocation, hitCount, condition, logMessage, suspendAllThreads); } @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); + return new EvaluatableBreakpoint(vm, this.getEventHub(), className, lineNumber, hitCount, condition, logMessage, suspendAllThreads); } @Override public IWatchpoint createWatchPoint(String className, String fieldName, String accessType, String condition, int hitCount) { - return new Watchpoint(vm, this.getEventHub(), className, fieldName, accessType, condition, hitCount); + return new Watchpoint(vm, this.getEventHub(), className, fieldName, accessType, condition, hitCount, suspendAllThreads); } @Override @@ -185,7 +192,7 @@ public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught if (exceptionTypes == null || exceptionTypes.length == 0) { ExceptionRequest request = manager.createExceptionRequest(null, notifyCaught, notifyUncaught); - request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + request.setSuspendPolicy(suspendAllThreads ? EventRequest.SUSPEND_ALL : EventRequest.SUSPEND_EVENT_THREAD); if (classFilters != null) { for (String classFilter : classFilters) { request.addClassFilter(classFilter); @@ -260,17 +267,22 @@ public VirtualMachine getVM() { return vm; } + @Override + public boolean shouldSuspendAllThreads() { + return suspendAllThreads; + } + @Override public IMethodBreakpoint createFunctionBreakpoint(String className, String functionName, String condition, int hitCount) { - return new MethodBreakpoint(vm, this.getEventHub(), className, functionName, condition, hitCount); + return new MethodBreakpoint(vm, this.getEventHub(), className, functionName, condition, hitCount, suspendAllThreads); } private void createExceptionBreakpoint(ReferenceType refType, boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters) { EventRequestManager manager = vm.eventRequestManager(); ExceptionRequest request = manager.createExceptionRequest(refType, notifyCaught, notifyUncaught); - request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + request.setSuspendPolicy(suspendAllThreads ? EventRequest.SUSPEND_ALL : EventRequest.SUSPEND_EVENT_THREAD); 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/DebugSettings.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java index 0a3e05ec8..b422f6801 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java @@ -45,6 +45,7 @@ public final class DebugSettings { public int jdwpRequestTimeout = 3000; public AsyncMode asyncJDWP = AsyncMode.OFF; public Switch debugSupportOnDecompiledSource = Switch.OFF; + public boolean suspendAllThreads = false; public static DebugSettings getCurrent() { return current; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugUtility.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugUtility.java index 4a2a49e9b..8a31792f0 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugUtility.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugUtility.java @@ -394,6 +394,7 @@ private static StepRequest createStepRequest(ThreadReference thread, int stepSiz request.addClassExclusionFilter(exclusionFilter); } } + // Note: suspend policy will be set by the caller (StepRequestHandler) based on session settings request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); request.addCountFilter(1); @@ -415,7 +416,7 @@ public static CompletableFuture stopOnEntry(IDebugSession debugSession, St EventRequestManager manager = debugSession.getVM().eventRequestManager(); MethodEntryRequest request = manager.createMethodEntryRequest(); request.addClassFilter(mainClass); - request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + request.setSuspendPolicy(debugSession.shouldSuspendAllThreads() ? EventRequest.SUSPEND_ALL : EventRequest.SUSPEND_EVENT_THREAD); debugSession.getEventHub().events().filter(debugEvent -> { return debugEvent.event instanceof MethodEntryEvent && request.equals(debugEvent.event.request()); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/EvaluatableBreakpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/EvaluatableBreakpoint.java index 723e2cadf..d5a909f0b 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/EvaluatableBreakpoint.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/EvaluatableBreakpoint.java @@ -29,28 +29,28 @@ public class EvaluatableBreakpoint extends Breakpoint implements IEvaluatableBre private Object compiledLogpointExpression = null; private Map compiledExpressions = new ConcurrentHashMap<>(); - EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber) { - this(vm, eventHub, className, lineNumber, 0, null); + EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, boolean suspendAllThreads) { + this(vm, eventHub, className, lineNumber, 0, null, suspendAllThreads); } - EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount) { - this(vm, eventHub, className, lineNumber, hitCount, null); + EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, boolean suspendAllThreads) { + this(vm, eventHub, className, lineNumber, hitCount, null, suspendAllThreads); } EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, - String condition) { - this(vm, eventHub, className, lineNumber, hitCount, condition, null); + String condition, boolean suspendAllThreads) { + this(vm, eventHub, className, lineNumber, hitCount, condition, null, suspendAllThreads); } EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, - String condition, String logMessage) { - super(vm, eventHub, className, lineNumber, hitCount, condition, logMessage); + String condition, String logMessage, boolean suspendAllThreads) { + super(vm, eventHub, className, lineNumber, hitCount, condition, logMessage, suspendAllThreads); this.eventHub = eventHub; } EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, JavaBreakpointLocation sourceLocation, int hitCount, - String condition, String logMessage) { - super(vm, eventHub, sourceLocation, hitCount, condition, logMessage); + String condition, String logMessage, boolean suspendAllThreads) { + super(vm, eventHub, sourceLocation, hitCount, condition, logMessage, suspendAllThreads); this.eventHub = eventHub; } 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..ec3ea818a 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 @@ -55,4 +55,11 @@ default void setAsync(boolean async) { default boolean async() { return false; } + + default void setSuspendPolicy(String policy) { + } + + default String getSuspendPolicy() { + return null; + } } 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..afc2283f0 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 @@ -52,4 +52,10 @@ void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, Strin IEventHub getEventHub(); VirtualMachine getVM(); + + /** + * Returns whether breakpoints should suspend all threads or just the event thread. + * This value is captured at session start and persists for the session lifetime. + */ + boolean shouldSuspendAllThreads(); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/MethodBreakpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/MethodBreakpoint.java index 7a6e74c6b..bc46fd96f 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/MethodBreakpoint.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/MethodBreakpoint.java @@ -44,6 +44,7 @@ public class MethodBreakpoint implements IMethodBreakpoint, IEvaluatableBreakpoi private String condition; private int hitCount; private boolean async = false; + private final boolean suspendAllThreads; private HashMap propertyMap = new HashMap<>(); private Object compiledConditionalExpression = null; @@ -53,7 +54,7 @@ public class MethodBreakpoint implements IMethodBreakpoint, IEvaluatableBreakpoi private List subscriptions = new ArrayList<>(); public MethodBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, String functionName, - String condition, int hitCount) { + String condition, int hitCount, boolean suspendAllThreads) { Objects.requireNonNull(vm); Objects.requireNonNull(eventHub); Objects.requireNonNull(className); @@ -64,6 +65,7 @@ public MethodBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, this.functionName = functionName; this.condition = condition; this.hitCount = hitCount; + this.suspendAllThreads = suspendAllThreads; } @Override @@ -262,7 +264,7 @@ private Optional createMethodEntryRequest0(ReferenceType typ MethodEntryRequest request = vm.eventRequestManager().createMethodEntryRequest(); request.addClassFilter(type); - request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + request.setSuspendPolicy(suspendAllThreads ? EventRequest.SUSPEND_ALL : EventRequest.SUSPEND_EVENT_THREAD); if (hitCount > 0) { request.addCountFilter(hitCount); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java index 3de321ec8..fdb2354a9 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java @@ -46,20 +46,22 @@ public class Watchpoint implements IWatchpoint, IEvaluatableBreakpoint { private HashMap propertyMap = new HashMap<>(); private Object compiledConditionalExpression = null; private Map compiledExpressions = new ConcurrentHashMap<>(); + private final boolean suspendAllThreads; // IDebugResource private List requests = new ArrayList<>(); private List subscriptions = new ArrayList<>(); - Watchpoint(VirtualMachine vm, IEventHub eventHub, String className, String fieldName) { - this(vm, eventHub, className, fieldName, "write"); + Watchpoint(VirtualMachine vm, IEventHub eventHub, String className, String fieldName, boolean suspendAllThreads) { + this(vm, eventHub, className, fieldName, "write", suspendAllThreads); } - Watchpoint(VirtualMachine vm, IEventHub eventHub, String className, String fieldName, String accessType) { - this(vm, eventHub, className, fieldName, accessType, null, 0); + Watchpoint(VirtualMachine vm, IEventHub eventHub, String className, String fieldName, String accessType, boolean suspendAllThreads) { + this(vm, eventHub, className, fieldName, accessType, null, 0, suspendAllThreads); } - Watchpoint(VirtualMachine vm, IEventHub eventHub, String className, String fieldName, String accessType, String condition, int hitCount) { + Watchpoint(VirtualMachine vm, IEventHub eventHub, String className, String fieldName, String accessType, + String condition, int hitCount, boolean suspendAllThreads) { Objects.requireNonNull(vm); Objects.requireNonNull(eventHub); Objects.requireNonNull(className); @@ -71,6 +73,7 @@ public class Watchpoint implements IWatchpoint, IEvaluatableBreakpoint { this.accessType = accessType; this.condition = condition; this.hitCount = hitCount; + this.suspendAllThreads = suspendAllThreads; } @Override @@ -212,7 +215,7 @@ private List createWatchpointRequests(ReferenceType type) { } watchpointRequests.forEach(request -> { - request.setSuspendPolicy(WatchpointRequest.SUSPEND_EVENT_THREAD); + request.setSuspendPolicy(suspendAllThreads ? EventRequest.SUSPEND_ALL : EventRequest.SUSPEND_EVENT_THREAD); if (hitCount > 0) { request.addCountFilter(hitCount); } 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..308adddb6 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 @@ -40,6 +40,7 @@ import com.sun.jdi.event.ThreadStartEvent; import com.sun.jdi.event.VMDeathEvent; import com.sun.jdi.event.VMDisconnectEvent; +import com.sun.jdi.request.EventRequest; import com.sun.jdi.event.VMStartEvent; public class ConfigurationDoneRequestHandler implements IDebugRequestHandler { @@ -119,7 +120,9 @@ 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 allThreadsStopped = event.request() != null + && event.request().suspendPolicy() == EventRequest.SUSPEND_ALL; + context.getProtocolServer().sendEvent(new Events.StoppedEvent("exception", thread.uniqueID(), allThreadsStopped)); debugEvent.shouldResume = false; } else { isImportantEvent = false; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestHandler.java index a615e8963..c38af257e 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestHandler.java @@ -26,7 +26,11 @@ public void destroyDebugSession(Command command, Arguments arguments, Response r IDebugSession debugSession = context.getDebugSession(); if (debugSession != null) { if (disconnectArguments.terminateDebuggee && !context.isAttached()) { - debugSession.terminate(); + try { + debugSession.terminate(); + } finally { + debugSession.detach(); + } } else { debugSession.detach(); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RestartFrameHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RestartFrameHandler.java index 164909656..26ebefaee 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RestartFrameHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RestartFrameHandler.java @@ -33,6 +33,7 @@ import com.sun.jdi.IncompatibleThreadStateException; import com.sun.jdi.StackFrame; import com.sun.jdi.ThreadReference; +import com.sun.jdi.request.EventRequest; import com.sun.jdi.request.StepRequest; /** @@ -121,7 +122,8 @@ private void stepInto(IDebugAdapterContext context, ThreadReference thread) { debugEvent.shouldResume = false; // Have to send two events to keep the UI sync with the step in operations: context.getProtocolServer().sendEvent(new Events.ContinuedEvent(thread.uniqueID())); - context.getProtocolServer().sendEvent(new Events.StoppedEvent("restartframe", thread.uniqueID())); + boolean allThreadsStopped = request.suspendPolicy() == EventRequest.SUSPEND_ALL; + context.getProtocolServer().sendEvent(new Events.StoppedEvent("restartframe", thread.uniqueID(), allThreadsStopped)); context.getThreadCache().setThreadStoppedReason(thread.uniqueID(), "restartframe"); }); request.enable(); 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..0f171486e 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 @@ -212,15 +212,19 @@ private void registerBreakpointHandler(IDebugAdapterContext context) { debugEvent.eventSet.resume(); } else { context.getThreadCache().addEventThread(bpThread, breakpointName); + boolean allThreadsStopped = event.request() != null + && event.request().suspendPolicy() == EventRequest.SUSPEND_ALL; context.getProtocolServer().sendEvent(new Events.StoppedEvent( - breakpointName, bpThread.uniqueID())); + breakpointName, bpThread.uniqueID(), allThreadsStopped)); } }); }); } else { context.getThreadCache().addEventThread(bpThread, breakpointName); + boolean allThreadsStopped = event.request() != null + && event.request().suspendPolicy() == EventRequest.SUSPEND_ALL; context.getProtocolServer().sendEvent(new Events.StoppedEvent( - breakpointName, bpThread.uniqueID())); + breakpointName, bpThread.uniqueID(), allThreadsStopped)); } debugEvent.shouldResume = false; } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java index 373b1c31b..c7c0995fe 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java @@ -41,6 +41,7 @@ import com.sun.jdi.ThreadReference; import com.sun.jdi.event.Event; import com.sun.jdi.event.WatchpointEvent; +import com.sun.jdi.request.EventRequest; public class SetDataBreakpointsRequestHandler implements IDebugRequestHandler { private boolean registered = false; @@ -152,13 +153,17 @@ private void registerWatchpointHandler(IDebugAdapterContext context) { debugEvent.eventSet.resume(); } else { context.getThreadCache().addEventThread(bpThread, "data breakpoint"); - context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID())); + boolean allThreadsStopped = event.request() != null + && event.request().suspendPolicy() == EventRequest.SUSPEND_ALL; + context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID(), allThreadsStopped)); } }); }); } else { context.getThreadCache().addEventThread(bpThread, "data breakpoint"); - context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID())); + boolean allThreadsStopped = event.request() != null + && event.request().suspendPolicy() == EventRequest.SUSPEND_ALL; + context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID(), allThreadsStopped)); } debugEvent.shouldResume = false; }); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java index 96a0e395b..01b5e9619 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java @@ -40,6 +40,7 @@ import com.microsoft.java.debug.core.protocol.Types.FunctionBreakpoint; import com.sun.jdi.ThreadReference; import com.sun.jdi.event.MethodEntryEvent; +import com.sun.jdi.request.EventRequest; public class SetFunctionBreakpointsRequestHandler implements IDebugRequestHandler { private boolean registered = false; @@ -166,16 +167,20 @@ private void registerMethodBreakpointHandler(IDebugAdapterContext context) { debugEvent.eventSet.resume(); } else { context.getThreadCache().addEventThread(bpThread, "function breakpoint"); + boolean allThreadsStopped = methodEntryEvent.request() != null + && methodEntryEvent.request().suspendPolicy() == EventRequest.SUSPEND_ALL; context.getProtocolServer().sendEvent(new Events.StoppedEvent( - "function breakpoint", bpThread.uniqueID())); + "function breakpoint", bpThread.uniqueID(), allThreadsStopped)); } }); }); } else { context.getThreadCache().addEventThread(bpThread, "function breakpoint"); + boolean allThreadsStopped = methodEntryEvent.request() != null + && methodEntryEvent.request().suspendPolicy() == EventRequest.SUSPEND_ALL; context.getProtocolServer() - .sendEvent(new Events.StoppedEvent("function breakpoint", bpThread.uniqueID())); + .sendEvent(new Events.StoppedEvent("function breakpoint", bpThread.uniqueID(), allThreadsStopped)); } debugEvent.shouldResume = false; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ThreadsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ThreadsRequestHandler.java index 6573f11da..5c4770391 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ThreadsRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ThreadsRequestHandler.java @@ -149,6 +149,11 @@ private CompletableFuture resume(Requests.ContinueArguments arguments, if (thread == null) { thread = DebugUtility.getThread(context.getDebugSession(), arguments.threadId); } + + if (context.getDebugSession().shouldSuspendAllThreads()) { + thread = null; + } + /** * See the jdi doc https://docs.oracle.com/javase/7/docs/jdk/api/jpda/jdi/com/sun/jdi/ThreadReference.html#resume(), * suspends of both the virtual machine and individual threads are counted. Before a thread will run again, it must diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java index 11a6391de..47854528f 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java @@ -65,6 +65,8 @@ import com.sun.jdi.StringReference; import com.sun.jdi.Type; import com.sun.jdi.Value; +import com.sun.jdi.PrimitiveType; +import com.sun.jdi.ClassNotLoadedException; public class VariablesRequestHandler implements IDebugRequestHandler { protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); @@ -82,7 +84,7 @@ public class VariablesRequestHandler implements IDebugRequestHandler { * single-threaded JDWP request processing strategy, * a single JDWP latency is about 10ms. */ - static final long USABLE_JDWP_LATENCY = 10/**ms*/; + static final long USABLE_JDWP_LATENCY = 10/*ms*/; @Override public List getTargetCommands() { @@ -370,10 +372,12 @@ public CompletableFuture handle(Command command, Arguments arguments, referenceId = context.getRecyclableIdPool().addObject(containerNode.getThreadId(), varProxy); } + String[] rawAttributes = extractAttributes(javaVariable); + Types.Variable typedVariables = new Types.Variable(name, valueString, typeString, referenceId, evaluateName); typedVariables.indexedVariables = Math.max(indexedVariables, 0); - if (varProxy != null && varProxy.isLazyVariable()) { - typedVariables.presentationHint = new VariablePresentationHint(true); + if ((varProxy != null && varProxy.isLazyVariable()) || (rawAttributes.length > 0)) { + typedVariables.presentationHint = new VariablePresentationHint(varProxy != null && varProxy.isLazyVariable(), rawAttributes); } if (detailsValue != null) { @@ -391,6 +395,36 @@ public CompletableFuture handle(Command command, Arguments arguments, return CompletableFuture.completedFuture(response); } + private String[] extractAttributes(Variable variable) { + final List attributes = new ArrayList<>(); + if (variable.field != null) { + if (variable.field.isStatic()) { + attributes.add("static"); + } + if (variable.field.isFinal()) { + // static final Primitive or StringRef fields are compile-time constants in Java + boolean isPrimitive; + try { + isPrimitive = variable.field.type() instanceof PrimitiveType; + } catch (ClassNotLoadedException e) { /* type() not yet loaded indicates some ReferenceType */ + isPrimitive = false; + } + boolean canBeTrueConstant = (isPrimitive || variable.field instanceof StringReference); + if (variable.field.isStatic() && canBeTrueConstant) { + attributes.add("constant"); + } + attributes.add("readOnly"); + } + } + // local 'final' variables get inlined by the compiler and do not appear + + if (variable.value instanceof StringReference) { + attributes.add("rawString"); + } + + return attributes.toArray(new String[0]); + } + private boolean supportsLogicStructureView(IDebugAdapterContext context) { return (!useAsyncJDWP(context) || context.getJDWPLatency() <= USABLE_JDWP_LATENCY) && DebugSettings.getCurrent().showLogicalStructure; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java index c6d0d38e0..3fd060625 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java @@ -163,6 +163,13 @@ public static boolean isLazyLoadingSupported(Value value) { return false; } String inheritedType = findInheritedType(value, COLLECTION_TYPES); + // Map.Entry should not use lazy loading because its details computation + // (getKey + getValue) is lightweight and showing "key:value" inline + // significantly improves UX for large Maps. See + // https://github.com/microsoft/vscode-java-debug/issues/1605 + if (Objects.equals(inheritedType, ENTRY_TYPE)) { + return false; + } if (inheritedType == null && !containsToStringMethod((ObjectReference) value)) { return false; } 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..9d32d4783 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 @@ -49,19 +49,13 @@ public static class StackFrame { /** * Constructs a StackFrame with the given information. * - * @param id - * the stack frame id - * @param name - * the stack frame name - * @param src - * source info of the stack frame - * @param ln - * line number of the stack frame - * @param col - * column number of the stack frame - * @param presentationHint - * An optional hint for how to present this frame in the UI. - * Values: 'normal', 'label', 'subtle' + * @param id the stack frame id + * @param name the stack frame name + * @param src source info of the stack frame + * @param ln line number of the stack frame + * @param col column number of the stack frame + * @param presentationHint An optional hint for how to present this frame in the UI. + * Values: 'normal', 'label', 'subtle' */ public StackFrame(int id, String name, Source src, int ln, int col, String presentationHint) { this.id = id; @@ -408,9 +402,11 @@ public static class ExceptionDetails { public static class VariablePresentationHint { public boolean lazy; + public String[] attributes; - public VariablePresentationHint(boolean lazy) { + public VariablePresentationHint(boolean lazy, String[] attributes) { this.lazy = lazy; + this.attributes = attributes; } } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java index 5652b922e..2c39425ed 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java @@ -466,15 +466,7 @@ private String getContents(IClassFile cf) { } private static String getFileURI(IClassFile classFile) { - String packageName = classFile.getParent().getElementName(); - String jarName = classFile.getParent().getParent().getElementName(); - try { - return new URI(JDT_SCHEME, "contents", PATH_SEPARATOR + jarName + PATH_SEPARATOR + packageName - + PATH_SEPARATOR + classFile.getElementName(), classFile.getHandleIdentifier(), null) - .toASCIIString(); - } catch (URISyntaxException e) { - return null; - } + return JDTUtils.toUri(classFile); } private static String getFileURI(IResource resource) { diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java index fc8189445..af9cdbc57 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java @@ -241,6 +241,13 @@ private boolean isMainMethod(IMethod method) { return method.isMainMethod(); } catch (JavaModelException e) { // do nothing + } catch (NoSuchMethodError e) { + // isMainMethodCandidate() was added in JDT Core 3.36, fall back to isMainMethod() + try { + return method.isMainMethod(); + } catch (JavaModelException ex) { + // do nothing + } } return false; diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainMethodHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainMethodHandler.java index be1092bb3..820bddd54 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainMethodHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainMethodHandler.java @@ -112,9 +112,13 @@ public static IMethod getMainMethod(IType type) throws JavaModelException { boolean allowInstanceMethod = isInstanceMainMethodSupported(type); List methods = new ArrayList<>(); for (IMethod method : type.getMethods()) { - if (method instanceof SourceMethod - && ((SourceMethod) method).isMainMethodCandidate()) { - methods.add(method); + try { + if (method instanceof SourceMethod + && ((SourceMethod) method).isMainMethodCandidate()) { + methods.add(method); + } + } catch (NoSuchMethodError e) { + // isMainMethodCandidate() was added in JDT Core 3.36, skip if unavailable } if (method.isMainMethod()) {