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 f9baa0e5d..660d4bbdd 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 @@ -74,8 +74,7 @@ public List getTargetCommands() { public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { if (context.getDebugSession() == null) { - return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EMPTY_DEBUG_SESSION, - "Debug Session doesn't exist."); + return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EMPTY_DEBUG_SESSION, "Debug Session doesn't exist."); } StepArguments stepArguments = (StepArguments) arguments; @@ -93,63 +92,59 @@ public CompletableFuture handle(Command command, Arguments arguments, ThreadState threadState = new ThreadState(); threadState.threadId = threadId; threadState.pendingStepType = command; - threadState.stackDepth = thread.frameCount(); - threadState.stepLocation = resolveLocation(thread, targetId, context); - threadState.targetStepIn = targetId > 0; threadState.eventSubscription = context.getDebugSession().getEventHub().events() - .filter(debugEvent -> (debugEvent.event instanceof StepEvent - && debugEvent.event.request().equals(threadState.pendingStepRequest)) - || (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); - }); + .filter(debugEvent -> (debugEvent.event instanceof StepEvent && debugEvent.event.request().equals(threadState.pendingStepRequest)) + || (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); + }); if (command == Command.STEPIN) { + String[] allowedClasses = threadState.pendingTargetStepIn != null + ? new String[]{threadState.pendingTargetStepIn.declaringTypeName} + : context.getStepFilters().allowClasses; threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread, - context.getStepFilters().allowClasses, - context.getStepFilters().skipClasses); + allowedClasses, + context.getStepFilters().skipClasses); } else if (command == Command.STEPOUT) { threadState.pendingStepRequest = DebugUtility.createStepOutRequest(thread, - context.getStepFilters().allowClasses, - context.getStepFilters().skipClasses); + context.getStepFilters().allowClasses, + context.getStepFilters().skipClasses); } else { threadState.pendingStepRequest = DebugUtility.createStepOverRequest(thread, null); } - threadState.pendingMethodExitRequest = thread.virtualMachine().eventRequestManager() - .createMethodExitRequest(); + threadState.pendingMethodExitRequest = thread.virtualMachine().eventRequestManager().createMethodExitRequest(); threadState.pendingMethodExitRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + LocationResponse locationResponse = resolveLocation(thread, targetId, context); if (context.asyncJDWP()) { List> futures = new ArrayList<>(); futures.add(AsyncJdwpUtils.runAsync(() -> { - try { - // JDWP Command: TR_FRAMES - threadState.topFrame = getTopFrame(targetThread); - threadState.stepLocation = threadState.topFrame.location(); - threadState.pendingMethodExitRequest - .addClassFilter(threadState.stepLocation.declaringType()); - if (targetThread.virtualMachine().canUseInstanceFilters()) { - try { - // JDWP Command: SF_THIS_OBJECT - ObjectReference thisObject = threadState.topFrame.thisObject(); - if (thisObject != null) { - threadState.pendingMethodExitRequest.addInstanceFilter(thisObject); - } - } catch (Exception e) { - // ignore + // JDWP Command: TR_FRAMES + threadState.stepLocation = locationResponse.location; + threadState.pendingTargetStepIn = locationResponse.methodInvocation; + threadState.targetStepIn = targetId > 0; + threadState.stepLocation = threadState.topFrame.location(); + threadState.pendingMethodExitRequest.addClassFilter(threadState.stepLocation.declaringType()); + if (targetThread.virtualMachine().canUseInstanceFilters()) { + try { + // JDWP Command: SF_THIS_OBJECT + ObjectReference thisObject = threadState.topFrame.thisObject(); + if (thisObject != null) { + threadState.pendingMethodExitRequest.addInstanceFilter(thisObject); } + } catch (Exception e) { + // ignore } - } catch (IncompatibleThreadStateException e) { - throw new CompletionException(e); } })); futures.add(AsyncJdwpUtils.runAsync( - // JDWP Command: OR_IS_COLLECTED - () -> threadState.pendingMethodExitRequest.addThreadFilter(targetThread))); + // JDWP Command: OR_IS_COLLECTED + () -> threadState.pendingMethodExitRequest.addThreadFilter(targetThread) + )); futures.add(AsyncJdwpUtils.runAsync(() -> { try { // JDWP Command: TR_FRAME_COUNT @@ -159,8 +154,9 @@ public CompletableFuture handle(Command command, Arguments arguments, } })); futures.add( - // JDWP Command: ER_SET - AsyncJdwpUtils.runAsync(() -> threadState.pendingStepRequest.enable())); + // JDWP Command: ER_SET + AsyncJdwpUtils.runAsync(() -> threadState.pendingStepRequest.enable()) + ); try { AsyncJdwpUtils.await(futures); @@ -175,7 +171,9 @@ public CompletableFuture handle(Command command, Arguments arguments, threadState.pendingMethodExitRequest.enable(); } else { threadState.stackDepth = targetThread.frameCount(); - threadState.topFrame = getTopFrame(targetThread); + threadState.stepLocation = locationResponse.location; + threadState.pendingTargetStepIn = locationResponse.methodInvocation; + threadState.targetStepIn = targetId > 0; threadState.stepLocation = threadState.topFrame.location(); threadState.pendingMethodExitRequest.addThreadFilter(thread); threadState.pendingMethodExitRequest.addClassFilter(threadState.stepLocation.declaringType()); @@ -199,81 +197,97 @@ public CompletableFuture handle(Command command, Arguments arguments, } catch (IncompatibleThreadStateException ex) { // Roll back the Exception info if stepping fails. context.getExceptionManager().setException(threadId, exception); - final String failureMessage = String.format( - "Failed to step because the thread '%s' is not suspended in the target VM.", thread.name()); + final String failureMessage = String.format("Failed to step because the thread '%s' is not suspended in the target VM.", thread.name()); throw AdapterUtils.createCompletionException( - failureMessage, - ErrorCode.STEP_FAILURE, - ex); + failureMessage, + ErrorCode.STEP_FAILURE, + ex); } catch (IndexOutOfBoundsException ex) { // Roll back the Exception info if stepping fails. context.getExceptionManager().setException(threadId, exception); - final String failureMessage = String.format( - "Failed to step because the thread '%s' doesn't contain any stack frame", thread.name()); - throw AdapterUtils.createCompletionException( - failureMessage, - ErrorCode.STEP_FAILURE, - ex); - } catch (AbsentInformationException ex) { - // Roll back the Exception info if stepping fails. - context.getExceptionManager().setException(threadId, exception); - final String failureMessage = String.format( - "Failed to step because the thread '%s' doesn't contain required line information in stack frame", - thread.name()); + final String failureMessage = String.format("Failed to step because the thread '%s' doesn't contain any stack frame", thread.name()); throw AdapterUtils.createCompletionException( - failureMessage, - ErrorCode.STEP_FAILURE, - ex); + failureMessage, + ErrorCode.STEP_FAILURE, + ex); } catch (Exception ex) { // Roll back the Exception info if stepping fails. context.getExceptionManager().setException(threadId, exception); - final String failureMessage = String.format("Failed to step because of the error '%s'", - ex.getMessage()); + final String failureMessage = String.format("Failed to step because of the error '%s'", ex.getMessage()); throw AdapterUtils.createCompletionException( - failureMessage, - ErrorCode.STEP_FAILURE, - ex.getCause() != null ? ex.getCause() : ex); + failureMessage, + ErrorCode.STEP_FAILURE, + ex.getCause() != null ? ex.getCause() : ex); } } return CompletableFuture.completedFuture(response); } - private Location resolveLocation(ThreadReference thread, int targetId, IDebugAdapterContext context) + private LocationResponse resolveLocation(ThreadReference thread, int targetId, IDebugAdapterContext context) throws IncompatibleThreadStateException, AbsentInformationException { if (targetId > 0) { Object value = context.getRecyclableIdPool().getObjectById(targetId); if (value instanceof MethodInvocation) { - MethodInvocation invocation = (MethodInvocation) value; - VirtualMachine vm = thread.virtualMachine(); - List refTypes = vm.classesByName(invocation.declaringTypeName); - for (ReferenceType referenceType : refTypes) { - Optional location = referenceType.allLineLocations().stream() - .filter(l -> matchesLocation(l, invocation)) - .findFirst(); - if (location.isPresent()) { - return location.get(); - } + return resolveLocation((MethodInvocation) value, thread); + } + } + return LocationResponse.absolute(getTopFrame(thread).location()); + } + + private LocationResponse resolveLocation(MethodInvocation invocation, ThreadReference thread) + throws AbsentInformationException, IncompatibleThreadStateException { + VirtualMachine vm = thread.virtualMachine(); + List refTypes = vm.classesByName(invocation.declaringTypeName); + + // if class is not yet loaded try to make the debugger step in until class is + // loaded. + if (refTypes.isEmpty()) { + return LocationResponse.waitFor(invocation, getTopFrame(thread).location()); + } else { + for (ReferenceType referenceType : refTypes) { + Optional location = referenceType.allLineLocations().stream() + .filter(l -> matchesLocation(l, invocation)) + .findFirst(); + if (location.isPresent()) { + return LocationResponse.absolute(location.get()); } } + return LocationResponse.absolute(getTopFrame(thread).location()); } - return getTopFrame(thread).location(); } private boolean matchesLocation(Location l, MethodInvocation invocation) { Method method = l.method(); - return method != null && Objects.equals(method.name(), invocation.methodName) + return method != null && Objects.equals(fixedName(method), invocation.methodName) && Objects.equals(method.signature(), invocation.methodSignature); } + /* + * Fix the name so for constructors we return empty to match the captured AST + * invocations. + */ + private String fixedName(Method method) { + String name = method.name(); + if ("".equals(name)) { + return ""; + } + return name; + } + private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, IDebugAdapterContext context, ThreadState threadState) { Event event = debugEvent.event; EventRequestManager eventRequestManager = debugSession.getVM().eventRequestManager(); - // When a breakpoint occurs, abort any pending step requests from the same - // thread. + // When a breakpoint occurs, abort any pending step requests from the same thread. if (event instanceof BreakpointEvent || event instanceof ExceptionEvent) { + // if we have a pending target step in then ignore and continue. + if (threadState.pendingTargetStepIn != null) { + debugEvent.shouldResume = true; + return; + } + long threadId = ((LocatableEvent) event).thread().uniqueID(); if (threadId == threadState.threadId && threadState.pendingStepRequest != null) { threadState.deleteStepRequest(eventRequestManager); @@ -291,21 +305,35 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, if (threadState.pendingStepType == Command.STEPIN) { int currentStackDepth = thread.frameCount(); Location currentStepLocation = getTopFrame(thread).location(); - // If the ending step location is filtered, or same as the original location - // where the step into operation is originated, + + // if we are in targetStepIn where the location was pending, then try to resolve + // the location and use it. + if (threadState.pendingTargetStepIn != null) { + LocationResponse newLocation = resolveLocation(threadState.pendingTargetStepIn, thread); + if (!newLocation.isLocationPending()) { + threadState.stepLocation = newLocation.location; + threadState.pendingTargetStepIn = null; + } + } + + // If the ending step location is filtered, or same as the original location where the step into operation is originated, // do another step of the same kind. if (shouldFilterLocation(threadState.stepLocation, currentStepLocation, context) || shouldDoExtraStepInto(threadState.stackDepth, threadState.stepLocation, - currentStackDepth, currentStepLocation, threadState.targetStepIn)) { + currentStackDepth, currentStepLocation, threadState.targetStepIn) + || threadState.pendingTargetStepIn != null) { + String[] allowedClasses = threadState.pendingTargetStepIn != null + ? new String[]{threadState.pendingTargetStepIn.declaringTypeName} + : context.getStepFilters().allowClasses; threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread, - context.getStepFilters().allowClasses, - context.getStepFilters().skipClasses); + allowedClasses, + context.getStepFilters().skipClasses); threadState.pendingStepRequest.enable(); debugEvent.shouldResume = true; return; } } - } catch (IncompatibleThreadStateException | IndexOutOfBoundsException ex) { + } catch (IncompatibleThreadStateException | IndexOutOfBoundsException | AbsentInformationException ex) { // ignore. } } @@ -319,8 +347,7 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, } else if (event instanceof MethodExitEvent) { MethodExitEvent methodExitEvent = (MethodExitEvent) event; long threadId = methodExitEvent.thread().uniqueID(); - if (threadId == threadState.threadId - && methodExitEvent.method().equals(threadState.stepLocation.method())) { + if (threadId == threadState.threadId && methodExitEvent.method().equals(threadState.stepLocation.method())) { Value returnValue = methodExitEvent.returnValue(); if (returnValue instanceof VoidValue) { context.getStepResultManager().removeMethodResult(threadId); @@ -338,26 +365,22 @@ private boolean isStepFiltersConfigured(StepFilters filters) { return false; } return ArrayUtils.isNotEmpty(filters.allowClasses) || ArrayUtils.isNotEmpty(filters.skipClasses) - || ArrayUtils.isNotEmpty(filters.classNameFilters) || filters.skipConstructors - || filters.skipStaticInitializers || filters.skipSynthetics; + || ArrayUtils.isNotEmpty(filters.classNameFilters) || filters.skipConstructors + || filters.skipStaticInitializers || filters.skipSynthetics; } /** - * Return true if the StepEvent's location is a Method that the user has - * indicated to filter. + * Return true if the StepEvent's location is a Method that the user has indicated to filter. * * @throws IncompatibleThreadStateException - * if the thread is not suspended in - * the target VM. + * if the thread is not suspended in the target VM. */ - private boolean shouldFilterLocation(Location originalLocation, Location currentLocation, - IDebugAdapterContext context) + private boolean shouldFilterLocation(Location originalLocation, Location currentLocation, IDebugAdapterContext context) throws IncompatibleThreadStateException { if (originalLocation == null || currentLocation == null) { return false; } - return !shouldFilterMethod(originalLocation.method(), context) - && shouldFilterMethod(currentLocation.method(), context); + return !shouldFilterMethod(originalLocation.method(), context) && shouldFilterMethod(currentLocation.method(), context); } private boolean shouldFilterMethod(Method method, IDebugAdapterContext context) { @@ -387,7 +410,6 @@ private boolean shouldDoExtraStepInto(int originalStackDepth, Location originalL return false; } } - Method originalMethod = originalLocation.method(); Method currentMethod = currentLocation.method(); if (!originalMethod.equals(currentMethod)) { @@ -403,14 +425,12 @@ private boolean shouldDoExtraStepInto(int originalStackDepth, Location originalL * Return the top stack frame of the target thread. * * @param thread - * the target thread. + * the target thread. * @return the top frame. * @throws IncompatibleThreadStateException - * if the thread is not suspended in - * the target VM. + * if the thread is not suspended in the target VM. * @throws IndexOutOfBoundsException - * if the thread doesn't contain any - * stack frame. + * if the thread doesn't contain any stack frame. */ private StackFrame getTopFrame(ThreadReference thread) throws IncompatibleThreadStateException { return thread.frame(0); @@ -426,6 +446,7 @@ class ThreadState { Location stepLocation = null; Disposable eventSubscription = null; boolean targetStepIn = false; + MethodInvocation pendingTargetStepIn; public void deleteMethodExitRequest(EventRequestManager manager) { DebugUtility.deleteEventRequestSafely(manager, this.pendingMethodExitRequest); @@ -437,4 +458,27 @@ public void deleteStepRequest(EventRequestManager manager) { this.pendingStepRequest = null; } } + + static class LocationResponse { + Location location; + MethodInvocation methodInvocation; + + private LocationResponse(Location location, MethodInvocation methodInvocation) { + this.location = location; + this.methodInvocation = methodInvocation; + } + + public static LocationResponse waitFor(MethodInvocation invocation, Location location) { + return new LocationResponse(location, invocation); + } + + public static LocationResponse absolute(Location location) { + return new LocationResponse(location, null); + } + + boolean isLocationPending() { + return methodInvocation != null; + } + + } } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BindingUtils.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BindingUtils.java index e30b0dd30..a6535715a 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BindingUtils.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BindingUtils.java @@ -34,7 +34,14 @@ private BindingUtils() { public static String getMethodName(IMethodBinding binding, boolean fromKey) { if (fromKey) { String key = binding.getKey(); - return key.substring(key.indexOf('.') + 1, key.indexOf('(')); + int dotAt = key.indexOf('.'); + int end = key.indexOf('<', dotAt); + if (end == -1) { + end = key.indexOf('('); + } else { + end = Math.min(end, key.indexOf('(')); + } + return key.substring(dotAt + 1, end); } else { return binding.getName(); } 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 eba9a42e6..eb3eb46f2 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 @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.logging.Level; @@ -51,6 +52,7 @@ import org.eclipse.jdt.core.dom.LambdaExpression; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.internal.core.JarPackageFragmentRoot; import org.eclipse.jdt.launching.IVMInstall; import org.eclipse.jdt.launching.JavaRuntime; @@ -487,13 +489,23 @@ public List findMethodInvocations(StackFrame frame) { invocation.expression = expression.toString(); IMethodBinding binding = entry.getValue(); invocation.methodName = binding.getName(); - invocation.declaringTypeName = binding.getDeclaringClass().getQualifiedName(); + if (binding.getDeclaringClass().isAnonymous()) { + ITypeBinding superclass = binding.getDeclaringClass().getSuperclass(); + if (superclass != null + && !superclass.isEqualTo(astUnit.getAST().resolveWellKnownType("java.lang.Object"))) { + invocation.declaringTypeName = superclass.getQualifiedName(); + } else { + return null; + } + } else { + invocation.declaringTypeName = binding.getDeclaringClass().getQualifiedName(); + } invocation.methodSignature = BindingUtils.toSignature(binding, BindingUtils.getMethodName(binding, true)); invocation.lineStart = astUnit.getLineNumber(expression.getStartPosition()); invocation.lineEnd = astUnit.getLineNumber(expression.getStartPosition() + expression.getLength()); invocation.columnStart = astUnit.getColumnNumber(expression.getStartPosition()); invocation.columnEnd = astUnit.getColumnNumber(expression.getStartPosition() + expression.getLength()); return invocation; - }).collect(Collectors.toList()); + }).filter(Objects::nonNull).collect(Collectors.toList()); } } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java index ab6efdaa2..f0364d98b 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java @@ -14,6 +14,7 @@ import java.util.Map; import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; @@ -25,7 +26,7 @@ public class MethodInvocationLocator extends ASTVisitor { private CompilationUnit unit; private Map targets; - private boolean collectMethodInvocations = false; + private int expressionCount = 0; public MethodInvocationLocator(int line, CompilationUnit unit) { super(false); @@ -40,24 +41,36 @@ public boolean visit(ExpressionStatement node) { int end = unit.getLineNumber(node.getStartPosition() + node.getLength()); if (line >= start && line <= end) { - collectMethodInvocations = true; + expressionCount++; } - return collectMethodInvocations; + return expressionCount > 0; } @Override public boolean visit(MethodInvocation node) { - int lineNumber = unit.getLineNumber(node.getStartPosition()); - if (lineNumber == this.line) { + if (expressionCount > 0) { targets.put(node, node.resolveMethodBinding()); - return true; } - return false; + return expressionCount > 0; + } + + @Override + public boolean visit(ClassInstanceCreation node) { + if (expressionCount > 0) { + targets.put(node, node.resolveConstructorBinding()); + expressionCount--; + } + return expressionCount > 0; } @Override public void endVisit(ExpressionStatement node) { - collectMethodInvocations = false; + expressionCount--; + } + + @Override + public void endVisit(ClassInstanceCreation node) { + expressionCount++; } public Map getTargets() {