diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/JdiMethodResult.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/JdiMethodResult.java new file mode 100644 index 000000000..715e23622 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/JdiMethodResult.java @@ -0,0 +1,25 @@ +/******************************************************************************* +* Copyright (c) 2020 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import com.sun.jdi.Method; +import com.sun.jdi.Value; + +public class JdiMethodResult { + public Method method; + public Value value; + + public JdiMethodResult(Method method, Value value) { + this.method = method; + this.value = value; + } +} 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 7b6636f7e..132d702d9 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 @@ -51,6 +51,8 @@ public class AdapterUtils { private static final String OS_NAME = System.getProperty("os.name", "").toLowerCase(); private static final Pattern ENCLOSING_CLASS_REGEX = Pattern.compile("^([^\\$]*)"); + public static final boolean isWin = isWindows(); + public static final boolean isMac = OS_NAME.contains("mac") || OS_NAME.contains("darwin"); /** * Check if the OS is windows or not. diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java index 248f0f803..395d39ec6 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2017 Microsoft Corporation and others. +* Copyright (c) 2017-2020 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -61,6 +61,7 @@ public class DebugAdapterContext implements IDebugAdapterContext { private IStackFrameManager stackFrameManager = new StackFrameManager(); private IExceptionManager exceptionManager = new ExceptionManager(); private IBreakpointManager breakpointManager = new BreakpointManager(); + private IStepResultManager stepResultManager = new StepResultManager(); public DebugAdapterContext(IProtocolServer server, IProviderContext providerContext) { this.providerContext = providerContext; @@ -320,4 +321,9 @@ public IExceptionManager getExceptionManager() { public IBreakpointManager getBreakpointManager() { return breakpointManager; } + + @Override + public IStepResultManager getStepResultManager() { + return stepResultManager; + } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java index 8f7055eee..4f843fd09 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Microsoft Corporation and others. + * Copyright (c) 2017-2020 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -126,4 +126,6 @@ public interface IDebugAdapterContext { IExceptionManager getExceptionManager(); IBreakpointManager getBreakpointManager(); + + IStepResultManager getStepResultManager(); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IStepResultManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IStepResultManager.java new file mode 100644 index 000000000..c19c0ecd6 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IStepResultManager.java @@ -0,0 +1,24 @@ +/******************************************************************************* +* Copyright (c) 2020 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import com.microsoft.java.debug.core.JdiMethodResult; + +public interface IStepResultManager { + JdiMethodResult setMethodResult(long threadId, JdiMethodResult methodResult); + + JdiMethodResult getMethodResult(long threadId); + + JdiMethodResult removeMethodResult(long threadId); + + void removeAllMethodResults(); +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/StepResultManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/StepResultManager.java new file mode 100644 index 000000000..dd3937181 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/StepResultManager.java @@ -0,0 +1,42 @@ +/******************************************************************************* +* Copyright (c) 2020 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.microsoft.java.debug.core.JdiMethodResult; + +public class StepResultManager implements IStepResultManager { + private Map methodResults = Collections.synchronizedMap(new HashMap<>()); + + @Override + public JdiMethodResult setMethodResult(long threadId, JdiMethodResult methodResult) { + return this.methodResults.put(threadId, methodResult); + } + + @Override + public JdiMethodResult getMethodResult(long threadId) { + return this.methodResults.get(threadId); + } + + @Override + public JdiMethodResult removeMethodResult(long threadId) { + return this.methodResults.remove(threadId); + } + + @Override + public void removeAllMethodResults() { + this.methodResults.clear(); + } +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java index a148ce20b..94e0d3078 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Microsoft Corporation and others. + * Copyright (c) 2017-2020 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -21,6 +21,7 @@ import com.microsoft.java.debug.core.DebugUtility; import com.microsoft.java.debug.core.IDebugSession; import com.microsoft.java.debug.core.JdiExceptionReference; +import com.microsoft.java.debug.core.JdiMethodResult; import com.microsoft.java.debug.core.adapter.AdapterUtils; import com.microsoft.java.debug.core.adapter.ErrorCode; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; @@ -34,11 +35,20 @@ import com.sun.jdi.IncompatibleThreadStateException; import com.sun.jdi.Location; import com.sun.jdi.Method; +import com.sun.jdi.ObjectReference; import com.sun.jdi.StackFrame; import com.sun.jdi.ThreadReference; +import com.sun.jdi.Value; +import com.sun.jdi.VoidValue; import com.sun.jdi.event.BreakpointEvent; import com.sun.jdi.event.Event; +import com.sun.jdi.event.ExceptionEvent; +import com.sun.jdi.event.LocatableEvent; +import com.sun.jdi.event.MethodExitEvent; import com.sun.jdi.event.StepEvent; +import com.sun.jdi.request.EventRequest; +import com.sun.jdi.request.EventRequestManager; +import com.sun.jdi.request.MethodExitRequest; import com.sun.jdi.request.StepRequest; import io.reactivex.disposables.Disposable; @@ -61,6 +71,7 @@ public CompletableFuture handle(Command command, Arguments arguments, ThreadReference thread = DebugUtility.getThread(context.getDebugSession(), threadId); if (thread != null) { JdiExceptionReference exception = context.getExceptionManager().removeException(threadId); + context.getStepResultManager().removeMethodResult(threadId); try { ThreadState threadState = new ThreadState(); threadState.threadId = threadId; @@ -69,7 +80,9 @@ public CompletableFuture handle(Command command, Arguments arguments, threadState.stepLocation = getTopFrame(thread).location(); threadState.eventSubscription = context.getDebugSession().getEventHub().events() .filter(debugEvent -> (debugEvent.event instanceof StepEvent && debugEvent.event.request().equals(threadState.pendingStepRequest)) - || debugEvent.event instanceof BreakpointEvent) + || (debugEvent.event instanceof MethodExitEvent && debugEvent.event.request().equals(threadState.pendingMethodExitRequest)) + || debugEvent.event instanceof BreakpointEvent + || debugEvent.event instanceof ExceptionEvent) .subscribe(debugEvent -> { handleDebugEvent(debugEvent, context.getDebugSession(), context, threadState); }); @@ -86,6 +99,24 @@ public CompletableFuture handle(Command command, Arguments arguments, threadState.pendingStepRequest = DebugUtility.createStepOverRequest(thread, null); } threadState.pendingStepRequest.enable(); + + MethodExitRequest methodExitRequest = thread.virtualMachine().eventRequestManager().createMethodExitRequest(); + methodExitRequest.addThreadFilter(thread); + methodExitRequest.addClassFilter(threadState.stepLocation.declaringType()); + if (thread.virtualMachine().canUseInstanceFilters()) { + try { + ObjectReference thisObject = getTopFrame(thread).thisObject(); + if (thisObject != null) { + methodExitRequest.addInstanceFilter(thisObject); + } + } catch (Exception e) { + // ignore + } + } + methodExitRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + threadState.pendingMethodExitRequest = methodExitRequest; + methodExitRequest.enable(); + DebugUtility.resumeThread(thread); ThreadsRequestHandler.checkThreadRunningAndRecycleIds(thread, context); @@ -116,19 +147,18 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, Event event = debugEvent.event; // When a breakpoint occurs, abort any pending step requests from the same thread. - if (event instanceof BreakpointEvent) { - long threadId = ((BreakpointEvent) event).thread().uniqueID(); + if (event instanceof BreakpointEvent || event instanceof ExceptionEvent) { + long threadId = ((LocatableEvent) event).thread().uniqueID(); if (threadId == threadState.threadId && threadState.pendingStepRequest != null) { - DebugUtility.deleteEventRequestSafely(debugSession.getVM().eventRequestManager(), threadState.pendingStepRequest); - threadState.pendingStepRequest = null; + threadState.deleteStepRequests(debugSession.getVM().eventRequestManager()); + context.getStepResultManager().removeMethodResult(threadId); if (threadState.eventSubscription != null) { threadState.eventSubscription.dispose(); } } } else if (event instanceof StepEvent) { ThreadReference thread = ((StepEvent) event).thread(); - DebugUtility.deleteEventRequestSafely(thread.virtualMachine().eventRequestManager(), threadState.pendingStepRequest); - threadState.pendingStepRequest = null; + threadState.deleteStepRequests(debugSession.getVM().eventRequestManager()); if (isStepFiltersConfigured(context.getStepFilters())) { try { if (threadState.pendingStepType == Command.STEPIN) { @@ -156,6 +186,19 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, } context.getProtocolServer().sendEvent(new Events.StoppedEvent("step", thread.uniqueID())); debugEvent.shouldResume = false; + } else if (event instanceof MethodExitEvent) { + MethodExitEvent methodExitEvent = (MethodExitEvent) event; + long threadId = methodExitEvent.thread().uniqueID(); + if (threadId == threadState.threadId && methodExitEvent.method().equals(threadState.stepLocation.method())) { + Value returnValue = methodExitEvent.returnValue(); + if (returnValue instanceof VoidValue) { + context.getStepResultManager().removeMethodResult(threadId); + } else { + JdiMethodResult methodResult = new JdiMethodResult(methodExitEvent.method(), returnValue); + context.getStepResultManager().setMethodResult(threadId, methodResult); + } + } + debugEvent.shouldResume = true; } } @@ -232,8 +275,16 @@ class ThreadState { long threadId = -1; Command pendingStepType; StepRequest pendingStepRequest = null; + MethodExitRequest pendingMethodExitRequest = null; int stackDepth = -1; Location stepLocation = null; Disposable eventSubscription = null; + + public void deleteStepRequests(EventRequestManager manager) { + DebugUtility.deleteEventRequestSafely(manager, this.pendingStepRequest); + DebugUtility.deleteEventRequestSafely(manager, this.pendingMethodExitRequest); + this.pendingMethodExitRequest = null; + this.pendingStepRequest = null; + } } } 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 5d4ee48f7..3e4b481d5 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 @@ -95,9 +95,11 @@ private CompletableFuture threads(Requests.ThreadsArguments arguments, private CompletableFuture pause(Requests.PauseArguments arguments, Response response, IDebugAdapterContext context) { ThreadReference thread = DebugUtility.getThread(context.getDebugSession(), arguments.threadId); if (thread != null) { + context.getStepResultManager().removeMethodResult(arguments.threadId); thread.suspend(); context.getProtocolServer().sendEvent(new Events.StoppedEvent("pause", arguments.threadId)); } else { + context.getStepResultManager().removeAllMethodResults(); context.getDebugSession().suspend(); context.getProtocolServer().sendEvent(new Events.StoppedEvent("pause", arguments.threadId, true)); } @@ -113,11 +115,13 @@ private CompletableFuture resume(Requests.ContinueArguments arguments, * be resumed (through ThreadReference#resume() or VirtualMachine#resume()) the same number of times it has been suspended. */ if (thread != null) { + context.getStepResultManager().removeMethodResult(arguments.threadId); context.getExceptionManager().removeException(arguments.threadId); allThreadsContinued = false; DebugUtility.resumeThread(thread); checkThreadRunningAndRecycleIds(thread, context); } else { + context.getStepResultManager().removeAllMethodResults(); context.getExceptionManager().removeAllExceptions(); context.getDebugSession().resume(); context.getRecyclableIdPool().removeAllObjects(); 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 45ed9fc34..37e6466a5 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 @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2017-2019 Microsoft Corporation and others. +* Copyright (c) 2017-2020 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -28,6 +28,7 @@ import com.microsoft.java.debug.core.Configuration; import com.microsoft.java.debug.core.DebugSettings; +import com.microsoft.java.debug.core.JdiMethodResult; import com.microsoft.java.debug.core.adapter.AdapterUtils; import com.microsoft.java.debug.core.adapter.ErrorCode; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; @@ -106,7 +107,13 @@ public CompletableFuture handle(Command command, Arguments arguments, ErrorCode.GET_VARIABLE_FAILURE); } try { - childrenList = VariableUtils.listLocalVariables(frame); + long threadId = stackFrameReference.getThread().uniqueID(); + JdiMethodResult result = context.getStepResultManager().getMethodResult(threadId); + if (result != null) { + String returnIcon = (AdapterUtils.isWin || AdapterUtils.isMac) ? "⎯►" : "->"; + childrenList.add(new Variable(returnIcon + result.method.name() + "()", result.value)); + } + childrenList.addAll(VariableUtils.listLocalVariables(frame)); Variable thisVariable = VariableUtils.getThisVariable(frame); if (thisVariable != null) { childrenList.add(thisVariable);