From a6ca87a0572d55ac9c289ba5a5a0f16318883a44 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Tue, 25 Oct 2022 17:17:32 +0800 Subject: [PATCH] Enable stepInTarget feature on more use cases --- .../core/adapter/ISourceLookUpProvider.java | 27 +- .../handler/StackTraceRequestHandler.java | 8 +- .../handler/StepInTargetsRequestHandler.java | 75 ++++- .../adapter/handler/StepRequestHandler.java | 276 +++++++++--------- .../variables/StackFrameReference.java | 12 +- .../microsoft/java/debug/BindingUtils.java | 7 +- .../internal/JdtSourceLookUpProvider.java | 83 +++--- .../internal/MethodInvocationLocator.java | 199 +++++++++++-- 8 files changed, 459 insertions(+), 228 deletions(-) diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java index 5e8d94451..ead10e33c 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java @@ -18,8 +18,6 @@ import com.microsoft.java.debug.core.JavaBreakpointLocation; import com.microsoft.java.debug.core.protocol.Types.SourceBreakpoint; -import com.sun.jdi.StackFrame; - public interface ISourceLookUpProvider extends IProvider { boolean supportsRealtimeBreakpointVerification(); @@ -70,17 +68,19 @@ default String getJavaRuntimeVersion(String projectName) { * Return method invocation found in the statement as the given line number of * the source file. * - * @param stackframe The stack frame where the invocation must be searched. + * @param uri The source file where the invocation must be searched. + * @param line The line number where the invocation must be searched. * * @return List of found method invocation or empty if not method invocations * can be found. */ - List findMethodInvocations(StackFrame stackframe); + List findMethodInvocations(String uri, int line); public static class MethodInvocation { public String expression; public String methodName; public String methodSignature; + public String methodGenericSignature; public String declaringTypeName; public int lineStart; public int lineEnd; @@ -89,8 +89,8 @@ public static class MethodInvocation { @Override public int hashCode() { - return Objects.hash(columnEnd, columnStart, declaringTypeName, expression, lineEnd, lineStart, methodName, - methodSignature); + return Objects.hash(expression, methodName, methodSignature, methodGenericSignature, declaringTypeName, + lineStart, lineEnd, columnStart, columnEnd); } @Override @@ -98,18 +98,15 @@ public boolean equals(Object obj) { if (this == obj) { return true; } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { + if (!(obj instanceof MethodInvocation)) { return false; } MethodInvocation other = (MethodInvocation) obj; - return columnEnd == other.columnEnd && columnStart == other.columnStart - && Objects.equals(declaringTypeName, other.declaringTypeName) - && Objects.equals(expression, other.expression) && lineEnd == other.lineEnd - && lineStart == other.lineStart && Objects.equals(methodName, other.methodName) - && Objects.equals(methodSignature, other.methodSignature); + return Objects.equals(expression, other.expression) && Objects.equals(methodName, other.methodName) + && Objects.equals(methodSignature, other.methodSignature) + && Objects.equals(methodGenericSignature, other.methodGenericSignature) + && Objects.equals(declaringTypeName, other.declaringTypeName) && lineStart == other.lineStart + && lineEnd == other.lineEnd && columnStart == other.columnStart && columnEnd == other.columnEnd; } } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java index f22966b03..fbfce49ef 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java @@ -89,10 +89,12 @@ public CompletableFuture handle(Command command, Arguments arguments, StackFrame[] frames = context.getStackFrameManager().reloadStackFrames(thread, stacktraceArgs.startFrame, count); List jdiFrames = resolveStackFrameInfos(frames, context.asyncJDWP()); for (int i = 0; i < count; i++) { - StackFrameReference stackframe = new StackFrameReference(thread, stacktraceArgs.startFrame + i); - int frameId = context.getRecyclableIdPool().addObject(stacktraceArgs.threadId, stackframe); + StackFrameReference frameReference = new StackFrameReference(thread, stacktraceArgs.startFrame + i); + int frameId = context.getRecyclableIdPool().addObject(stacktraceArgs.threadId, frameReference); StackFrameInfo jdiFrame = jdiFrames.get(i); - result.add(convertDebuggerStackFrameToClient(jdiFrame, frameId, i == 0, context)); + Types.StackFrame lspFrame = convertDebuggerStackFrameToClient(jdiFrame, frameId, i == 0, context); + result.add(lspFrame); + frameReference.setSource(lspFrame.source); } } catch (IncompatibleThreadStateException | IndexOutOfBoundsException | URISyntaxException | AbsentInformationException | ObjectCollectedException diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepInTargetsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepInTargetsRequestHandler.java index f61d2a8b9..5b3b735fe 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepInTargetsRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepInTargetsRequestHandler.java @@ -10,13 +10,19 @@ *******************************************************************************/ package com.microsoft.java.debug.core.adapter.handler; +import java.io.File; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.logging.Logger; +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.adapter.AdapterUtils; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider; @@ -27,10 +33,14 @@ import com.microsoft.java.debug.core.protocol.Requests.Command; import com.microsoft.java.debug.core.protocol.Requests.StepInTargetsArguments; import com.microsoft.java.debug.core.protocol.Responses.StepInTargetsResponse; +import com.microsoft.java.debug.core.protocol.Types.Source; import com.microsoft.java.debug.core.protocol.Types.StepInTarget; +import com.sun.jdi.AbsentInformationException; +import com.sun.jdi.ReferenceType; import com.sun.jdi.StackFrame; public class StepInTargetsRequestHandler implements IDebugRequestHandler { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); @Override public List getTargetCommands() { @@ -45,37 +55,84 @@ public CompletableFuture handle(Command command, Arguments arguments, final int frameId = stepInTargetsArguments.frameId; return CompletableFuture.supplyAsync(() -> { response.body = new StepInTargetsResponse( - findFrame(frameId, context).map(f -> findTargets(f.thread().uniqueID(), f, context)) + findFrame(frameId, context).map(f -> findTargets(f, context)) .orElse(Collections.emptyList()).toArray(StepInTarget[]::new)); return response; }); } - private Optional findFrame(int frameId, IDebugAdapterContext context) { + private Optional findFrame(int frameId, IDebugAdapterContext context) { Object object = context.getRecyclableIdPool().getObjectById(frameId); if (object instanceof StackFrameReference) { - return Optional.of(context.getStackFrameManager().getStackFrame((StackFrameReference) object)); + return Optional.of((StackFrameReference) object); } return Optional.empty(); } - private List findTargets(long threadId, StackFrame stackframe, IDebugAdapterContext context) { + private List findTargets(StackFrameReference frameReference, IDebugAdapterContext context) { + StackFrame stackframe = context.getStackFrameManager().getStackFrame(frameReference); + if (stackframe == null) { + return Collections.emptyList(); + } + + Source source = frameReference.getSource() == null ? findSource(stackframe, context) : frameReference.getSource(); + if (source == null) { + return Collections.emptyList(); + } + + String sourceUri = AdapterUtils.convertPath(source.path, AdapterUtils.isUri(source.path), true); + if (sourceUri == null) { + return Collections.emptyList(); + } + ISourceLookUpProvider sourceLookUpProvider = context.getProvider(ISourceLookUpProvider.class); - List invocations = sourceLookUpProvider.findMethodInvocations(stackframe); + List invocations = sourceLookUpProvider.findMethodInvocations(sourceUri, stackframe.location().lineNumber()); if (invocations.isEmpty()) { return Collections.emptyList(); } + long threadId = stackframe.thread().uniqueID(); List targets = new ArrayList<>(invocations.size()); for (MethodInvocation methodInvocation : invocations) { int id = context.getRecyclableIdPool().addObject(threadId, methodInvocation); StepInTarget target = new StepInTarget(id, methodInvocation.expression); - target.column = methodInvocation.columnStart; - target.endColumn = methodInvocation.columnEnd; - target.line = methodInvocation.lineStart; - target.endLine = methodInvocation.lineEnd; + target.column = AdapterUtils.convertColumnNumber(methodInvocation.columnStart, + context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1()); + target.endColumn = AdapterUtils.convertColumnNumber(methodInvocation.columnEnd, + context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1()); + target.line = AdapterUtils.convertLineNumber(methodInvocation.lineStart, + context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1()); + target.endLine = AdapterUtils.convertLineNumber(methodInvocation.lineEnd, + context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1()); targets.add(target); } + + // TODO remove the executed method calls. return targets; } + + private Source findSource(StackFrame frame, IDebugAdapterContext context) { + ReferenceType declaringType = frame.location().declaringType(); + String typeName = declaringType.name(); + String sourceName = null; + String sourcePath = null; + try { + // When the .class file doesn't contain source information in meta data, + // invoking ReferenceType#sourceName() would throw AbsentInformationException. + sourceName = declaringType.sourceName(); + sourcePath = declaringType.sourcePaths(null).get(0); + } catch (AbsentInformationException e) { + String enclosingType = AdapterUtils.parseEnclosingType(typeName); + sourceName = enclosingType.substring(enclosingType.lastIndexOf('.') + 1) + ".java"; + sourcePath = enclosingType.replace('.', File.separatorChar) + ".java"; + } + + try { + return StackTraceRequestHandler.convertDebuggerSourceToClient(typeName, sourceName, sourcePath, context); + } catch (URISyntaxException e) { + logger.log(Level.SEVERE, "Failed to resolve the source info of the stack frame.", e); + } + + return null; + } } 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 660d4bbdd..bd324a7fa 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 @@ -15,7 +15,6 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -39,8 +38,9 @@ import com.microsoft.java.debug.core.protocol.Requests.StepArguments; import com.microsoft.java.debug.core.protocol.Requests.StepFilters; import com.microsoft.java.debug.core.protocol.Requests.StepInArguments; -import com.sun.jdi.AbsentInformationException; +import com.sun.jdi.ClassType; import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.InterfaceType; import com.sun.jdi.Location; import com.sun.jdi.Method; import com.sun.jdi.ObjectReference; @@ -48,7 +48,6 @@ import com.sun.jdi.StackFrame; import com.sun.jdi.ThreadReference; import com.sun.jdi.Value; -import com.sun.jdi.VirtualMachine; import com.sun.jdi.VoidValue; import com.sun.jdi.event.BreakpointEvent; import com.sun.jdi.event.Event; @@ -102,11 +101,8 @@ public CompletableFuture handle(Command command, Arguments arguments, }); if (command == Command.STEPIN) { - String[] allowedClasses = threadState.pendingTargetStepIn != null - ? new String[]{threadState.pendingTargetStepIn.declaringTypeName} - : context.getStepFilters().allowClasses; threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread, - allowedClasses, + context.getStepFilters().allowClasses, context.getStepFilters().skipClasses); } else if (command == Command.STEPOUT) { threadState.pendingStepRequest = DebugUtility.createStepOutRequest(thread, @@ -119,26 +115,29 @@ public CompletableFuture handle(Command command, Arguments arguments, threadState.pendingMethodExitRequest = thread.virtualMachine().eventRequestManager().createMethodExitRequest(); threadState.pendingMethodExitRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); - LocationResponse locationResponse = resolveLocation(thread, targetId, context); + threadState.targetStepIn = targetId > 0 + ? (MethodInvocation) context.getRecyclableIdPool().getObjectById(targetId) : null; if (context.asyncJDWP()) { List> futures = new ArrayList<>(); futures.add(AsyncJdwpUtils.runAsync(() -> { // 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); + try { + 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 } - } catch (Exception e) { - // ignore } + } catch (IncompatibleThreadStateException e1) { + throw new CompletionException(e1); } })); futures.add(AsyncJdwpUtils.runAsync( @@ -170,10 +169,8 @@ public CompletableFuture handle(Command command, Arguments arguments, // JDWP Command: ER_SET threadState.pendingMethodExitRequest.enable(); } else { + threadState.topFrame = getTopFrame(targetThread); threadState.stackDepth = targetThread.frameCount(); - 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()); @@ -224,57 +221,6 @@ public CompletableFuture handle(Command command, Arguments arguments, return CompletableFuture.completedFuture(response); } - 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) { - 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()); - } - } - - private boolean matchesLocation(Location l, MethodInvocation invocation) { - Method method = l.method(); - 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; @@ -283,7 +229,7 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, // 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) { + if (threadState.targetStepIn != null) { debugEvent.shouldResume = true; return; } @@ -299,41 +245,62 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, } } else if (event instanceof StepEvent) { ThreadReference thread = ((StepEvent) event).thread(); + long threadId = thread.uniqueID(); threadState.deleteStepRequest(eventRequestManager); - if (isStepFiltersConfigured(context.getStepFilters()) || threadState.targetStepIn) { + if (isStepFiltersConfigured(context.getStepFilters()) || threadState.targetStepIn != null) { try { - if (threadState.pendingStepType == Command.STEPIN) { + if (threadState.pendingStepType == Command.STEPIN || threadState.targetStepIn != null) { int currentStackDepth = thread.frameCount(); - Location currentStepLocation = getTopFrame(thread).location(); - - // 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; + StackFrame topFrame = getTopFrame(thread); + Location currentStepLocation = topFrame.location(); + if (threadState.targetStepIn != null) { + if (isStoppedAtSelectedMethod(topFrame, threadState.targetStepIn)) { + // hit: send StoppedEvent + } else { + if (currentStackDepth > threadState.stackDepth) { + context.getStepResultManager().removeMethodResult(threadId); + threadState.pendingStepRequest = DebugUtility.createStepOutRequest(thread, + context.getStepFilters().allowClasses, + context.getStepFilters().skipClasses); + threadState.pendingStepRequest.enable(); + debugEvent.shouldResume = true; + return; + } else if (currentStackDepth == threadState.stackDepth) { + // If the ending step location is same as the original location where the step into operation is originated, + // do another step of the same kind. + if (isSameLocation(currentStepLocation, threadState.stepLocation)) { + context.getStepResultManager().removeMethodResult(threadId); + threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread, + context.getStepFilters().allowClasses, + context.getStepFilters().skipClasses); + threadState.pendingStepRequest.enable(); + debugEvent.shouldResume = true; + return; + } + } } - } - - // 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) + } else if (shouldFilterLocation(threadState.stepLocation, currentStepLocation, context) || shouldDoExtraStepInto(threadState.stackDepth, threadState.stepLocation, - 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, + currentStackDepth, currentStepLocation)) { + // 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. + context.getStepResultManager().removeMethodResult(threadId); + String[] allowedClasses = context.getStepFilters().allowClasses; + if (currentStackDepth > threadState.stackDepth) { + threadState.pendingStepRequest = DebugUtility.createStepOutRequest(thread, allowedClasses, - context.getStepFilters().skipClasses); + context.getStepFilters().skipClasses); + } else { + threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread, + allowedClasses, + context.getStepFilters().skipClasses); + } threadState.pendingStepRequest.enable(); debugEvent.shouldResume = true; return; } } - } catch (IncompatibleThreadStateException | IndexOutOfBoundsException | AbsentInformationException ex) { + } catch (IncompatibleThreadStateException | IndexOutOfBoundsException ex) { // ignore. } } @@ -360,6 +327,56 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, } } + private boolean isStoppedAtSelectedMethod(StackFrame frame, MethodInvocation selectedMethod) { + Method method = frame.location().method(); + if (method != null + && Objects.equals(method.name(), selectedMethod.methodName) + && (Objects.equals(method.signature(), selectedMethod.methodSignature) + || Objects.equals(method.genericSignature(), selectedMethod.methodGenericSignature))) { + ObjectReference thisObject = frame.thisObject(); + ReferenceType currentType = (thisObject == null) ? method.declaringType() : thisObject.referenceType(); + if ("java.lang.Object".equals(selectedMethod.declaringTypeName)) { + return true; + } + + return isSubType(currentType, selectedMethod.declaringTypeName); + } + + return false; + } + + private boolean isSubType(ReferenceType currentType, String baseType) { + if (baseType.equals(currentType.name())) { + return true; + } + + if (currentType instanceof ClassType) { + ClassType classType = (ClassType) currentType; + ClassType superClassType = classType.superclass(); + if (superClassType != null && isSubType(superClassType, baseType)) { + return true; + } + + List interfaces = classType.allInterfaces(); + for (InterfaceType iface : interfaces) { + if (isSubType(iface, baseType)) { + return true; + } + } + } + + if (currentType instanceof InterfaceType) { + List superInterfaces = ((InterfaceType) currentType).superinterfaces(); + for (InterfaceType superInterface : superInterfaces) { + if (isSubType(superInterface, baseType)) { + return true; + } + } + } + + return false; + } + private boolean isStepFiltersConfigured(StepFilters filters) { if (filters == null) { return false; @@ -400,25 +417,36 @@ private boolean shouldFilterMethod(Method method, IDebugAdapterContext context) * the target VM. */ private boolean shouldDoExtraStepInto(int originalStackDepth, Location originalLocation, int currentStackDepth, - Location currentLocation, boolean targetStepIn) + Location currentLocation) throws IncompatibleThreadStateException { - if (!targetStepIn) { - if (originalStackDepth != currentStackDepth) { - return false; - } - if (originalLocation == null) { - return false; - } + if (originalStackDepth != currentStackDepth) { + return false; + } + if (originalLocation == null) { + return false; } + Method originalMethod = originalLocation.method(); Method currentMethod = currentLocation.method(); if (!originalMethod.equals(currentMethod)) { - return targetStepIn; + return false; } if (originalLocation.lineNumber() != currentLocation.lineNumber()) { - return targetStepIn; + return false; } - return !targetStepIn; + + return true; + } + + private boolean isSameLocation(Location original, Location current) { + if (original == null || current == null) { + return false; + } + + Method originalMethod = original.method(); + Method currentMethod = current.method(); + return originalMethod.equals(currentMethod) + && original.lineNumber() == current.lineNumber(); } /** @@ -445,8 +473,7 @@ class ThreadState { StackFrame topFrame = null; Location stepLocation = null; Disposable eventSubscription = null; - boolean targetStepIn = false; - MethodInvocation pendingTargetStepIn; + MethodInvocation targetStepIn = null; public void deleteMethodExitRequest(EventRequestManager manager) { DebugUtility.deleteEventRequestSafely(manager, this.pendingMethodExitRequest); @@ -458,27 +485,4 @@ 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.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/StackFrameReference.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/StackFrameReference.java index 4dc895007..46726cde8 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/StackFrameReference.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/StackFrameReference.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Microsoft Corporation and others. + * Copyright (c) 2017-2022 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 @@ -11,12 +11,14 @@ package com.microsoft.java.debug.core.adapter.variables; +import com.microsoft.java.debug.core.protocol.Types.Source; import com.sun.jdi.ThreadReference; public class StackFrameReference { private final int depth; private final int hash; private final ThreadReference thread; + private Source source; /** * Create a wrapper of JDI stackframe to keep the immutable properties of a stackframe, IStackFrameManager will use @@ -48,6 +50,14 @@ public ThreadReference getThread() { return thread; } + public Source getSource() { + return source; + } + + public void setSource(Source source) { + this.source = source; + } + @Override public int hashCode() { return hash; 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 a6535715a..b696be049 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017-2020 Microsoft Corporation and others. + * Copyright (c) 2017-2022 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,12 +61,9 @@ public static String toSignature(IMethodBinding binding, String name) { // use key for now until JDT core provides a public API for this. // "Ljava/util/Arrays;.asList([TT;)Ljava/util/List;" // "([Ljava/lang/String;)V|Ljava/lang/InterruptedException;" - if (!binding.getName().equals(name)) { - throw new IllegalArgumentException("The method name and binding method name doesn't match."); - } - String signatureString = binding.getKey(); if (signatureString != null) { + name = "." + name; int index = signatureString.indexOf(name); if (index > -1) { int exceptionIndex = signatureString.indexOf("|", signatureString.lastIndexOf(")")); 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 eb3eb46f2..c4d4fe318 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 @@ -15,7 +15,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; -import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; @@ -35,28 +34,31 @@ import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.URIUtil; import org.eclipse.debug.core.sourcelookup.ISourceContainer; import org.eclipse.jdt.core.IBuffer; import org.eclipse.jdt.core.IClassFile; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragmentRoot; -import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.LambdaExpression; -import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.manipulation.CoreASTProvider; 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; import org.eclipse.jdt.launching.LibraryLocation; +import org.eclipse.jdt.ls.core.internal.JDTUtils; import com.microsoft.java.debug.BindingUtils; import com.microsoft.java.debug.BreakpointLocationLocator; @@ -71,8 +73,6 @@ import com.microsoft.java.debug.core.protocol.Types.BreakpointLocation; import com.microsoft.java.debug.core.protocol.Types.SourceBreakpoint; -import com.sun.jdi.StackFrame; - public class JdtSourceLookUpProvider implements ISourceLookUpProvider { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); private static final String JDT_SCHEME = "jdt"; @@ -452,60 +452,71 @@ private static String resolveSystemLibraryVersion(IJavaProject project, IVMInsta return null; } - @Override - public List findMethodInvocations(StackFrame frame) { - if (frame == null) { - throw new IllegalArgumentException("frame is null"); - } - - IJavaProject project = JdtUtils.findProject(frame, getSourceContainers()); - if (project == null) { - logger.log(Level.WARNING, - String.format("Failed to resolve project for the frame: %s", frame)); + public List findMethodInvocations(String uri, int line) { + if (uri == null) { return Collections.emptyList(); } - String uri; - try { - IType type = project.findType(JdtUtils.getDeclaringTypeName(frame)); - uri = type.getResource().getLocationURI().toURL().toString(); - } catch (JavaModelException | DebugException | MalformedURLException e) { - logger.log(Level.SEVERE, - String.format("Failed to resolve type for the frame: %s", frame)); - return Collections.emptyList(); + boolean useCache = false; + CompilationUnit cachedUnit = CoreASTProvider.getInstance().getCachedAST(); + if (cachedUnit != null) { + ITypeRoot cachedElement = cachedUnit.getTypeRoot(); + if (cachedElement != null && isSameURI(JDTUtils.toUri(cachedElement), uri)) { + useCache = true; + } } - CompilationUnit astUnit = asCompilationUnit(uri); + final CompilationUnit astUnit = useCache ? cachedUnit : asCompilationUnit(uri); if (astUnit == null) { return Collections.emptyList(); } - MethodInvocationLocator locator = new MethodInvocationLocator(frame.location().lineNumber(), astUnit); + MethodInvocationLocator locator = new MethodInvocationLocator(line, astUnit); astUnit.accept(locator); return locator.getTargets().entrySet().stream().map(entry -> { MethodInvocation invocation = new MethodInvocation(); - Expression expression = entry.getKey(); - invocation.expression = expression.toString(); - IMethodBinding binding = entry.getValue(); - invocation.methodName = binding.getName(); + ASTNode astNode = entry.getKey(); + invocation.expression = astNode.toString(); + IMethodBinding binding = entry.getValue().getMethodDeclaration(); + invocation.methodName = binding.isConstructor() ? "" : binding.getName(); if (binding.getDeclaringClass().isAnonymous()) { ITypeBinding superclass = binding.getDeclaringClass().getSuperclass(); if (superclass != null && !superclass.isEqualTo(astUnit.getAST().resolveWellKnownType("java.lang.Object"))) { - invocation.declaringTypeName = superclass.getQualifiedName(); + invocation.declaringTypeName = superclass.getBinaryName(); } else { return null; } } else { - invocation.declaringTypeName = binding.getDeclaringClass().getQualifiedName(); + // Keep consistent with JDI since JDI uses binary class name + invocation.declaringTypeName = binding.getDeclaringClass().getBinaryName(); } - 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()); + invocation.methodGenericSignature = BindingUtils.toSignature(binding, BindingUtils.getMethodName(binding, true)); + invocation.methodSignature = Signature.getTypeErasure(invocation.methodGenericSignature); + int startOffset = astNode.getStartPosition(); + if (astNode instanceof org.eclipse.jdt.core.dom.MethodInvocation) { + // The range covered by the stepIn target should start with the method name. + startOffset = ((org.eclipse.jdt.core.dom.MethodInvocation) astNode).getName().getStartPosition(); + } + invocation.lineStart = astUnit.getLineNumber(startOffset); + invocation.columnStart = astUnit.getColumnNumber(startOffset); + int endOffset = astNode.getStartPosition() + astNode.getLength(); + invocation.lineEnd = astUnit.getLineNumber(endOffset); + invocation.columnEnd = astUnit.getColumnNumber(endOffset); return invocation; }).filter(Objects::nonNull).collect(Collectors.toList()); } + + private boolean isSameURI(String uri1, String uri2) { + if (Objects.equals(uri1, uri2)) { + return true; + } + + try { + return URIUtil.sameURI(new URI(uri1), new URI(uri2)); + } catch (URISyntaxException e) { + return false; + } + } } 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 f0364d98b..a25fbc3f6 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 @@ -13,20 +13,44 @@ import java.util.HashMap; import java.util.Map; +import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; +import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; +import org.eclipse.jdt.core.dom.AssertStatement; +import org.eclipse.jdt.core.dom.BreakStatement; 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.ConstructorInvocation; +import org.eclipse.jdt.core.dom.ContinueStatement; +import org.eclipse.jdt.core.dom.DoStatement; +import org.eclipse.jdt.core.dom.EmptyStatement; +import org.eclipse.jdt.core.dom.EnhancedForStatement; +import org.eclipse.jdt.core.dom.EnumDeclaration; import org.eclipse.jdt.core.dom.ExpressionStatement; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.ForStatement; import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.IfStatement; +import org.eclipse.jdt.core.dom.LabeledStatement; +import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.ReturnStatement; +import org.eclipse.jdt.core.dom.SuperConstructorInvocation; +import org.eclipse.jdt.core.dom.SwitchStatement; +import org.eclipse.jdt.core.dom.SynchronizedStatement; +import org.eclipse.jdt.core.dom.ThrowStatement; +import org.eclipse.jdt.core.dom.TryStatement; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.TypeDeclarationStatement; +import org.eclipse.jdt.core.dom.VariableDeclarationStatement; +import org.eclipse.jdt.core.dom.WhileStatement; +import org.eclipse.jdt.core.dom.YieldStatement; public class MethodInvocationLocator extends ASTVisitor { private int line; private CompilationUnit unit; - private Map targets; - - private int expressionCount = 0; + private Map targets; public MethodInvocationLocator(int line, CompilationUnit unit) { super(false); @@ -35,45 +59,174 @@ public MethodInvocationLocator(int line, CompilationUnit unit) { this.targets = new HashMap<>(); } + @Override + public boolean visit(FieldDeclaration node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(MethodDeclaration node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(TypeDeclaration node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(AnonymousClassDeclaration node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(EnumDeclaration node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(AnnotationTypeDeclaration node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(VariableDeclarationStatement node) { + return shouldVisitNode(node); + } + @Override public boolean visit(ExpressionStatement node) { - int start = unit.getLineNumber(node.getStartPosition()); - int end = unit.getLineNumber(node.getStartPosition() + node.getLength()); + return shouldVisitNode(node); + } + + @Override + public boolean visit(AssertStatement node) { + return shouldVisitNode(node); - if (line >= start && line <= end) { - expressionCount++; - } - return expressionCount > 0; } @Override - public boolean visit(MethodInvocation node) { - if (expressionCount > 0) { - targets.put(node, node.resolveMethodBinding()); + public boolean visit(BreakStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(ContinueStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(DoStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(EmptyStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(EnhancedForStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(ForStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(IfStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(LabeledStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(ReturnStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(SwitchStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(SynchronizedStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(ThrowStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(TryStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(TypeDeclarationStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(WhileStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(YieldStatement node) { + return shouldVisitNode(node); + } + + @Override + public boolean visit(ConstructorInvocation node) { + if (shouldVisitNode(node)) { + targets.put(node, node.resolveConstructorBinding()); + return true; } - return expressionCount > 0; + return false; } @Override - public boolean visit(ClassInstanceCreation node) { - if (expressionCount > 0) { + public boolean visit(SuperConstructorInvocation node) { + if (shouldVisitNode(node)) { targets.put(node, node.resolveConstructorBinding()); - expressionCount--; + return true; } - return expressionCount > 0; + return false; } @Override - public void endVisit(ExpressionStatement node) { - expressionCount--; + public boolean visit(MethodInvocation node) { + targets.put(node, node.resolveMethodBinding()); + return true; } @Override - public void endVisit(ClassInstanceCreation node) { - expressionCount++; + public boolean visit(ClassInstanceCreation node) { + targets.put(node, node.resolveConstructorBinding()); + return true; + } + + private boolean shouldVisitNode(ASTNode node) { + int start = unit.getLineNumber(node.getStartPosition()); + int end = unit.getLineNumber(node.getStartPosition() + node.getLength()); + + if (line >= start && line <= end) { + return true; + } + + return false; } - public Map getTargets() { + public Map getTargets() { return targets; } }