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 790eff801..3a8d88316 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 @@ -18,6 +18,7 @@ import java.util.concurrent.CompletableFuture; import com.sun.jdi.Location; +import com.sun.jdi.Method; import com.sun.jdi.ReferenceType; import com.sun.jdi.VMDisconnectedException; import com.sun.jdi.VirtualMachine; @@ -38,6 +39,7 @@ public class Breakpoint implements IBreakpoint { private String condition = null; private String logMessage = null; private HashMap propertyMap = new HashMap<>(); + private String methodSignature = null; Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber) { this(vm, eventHub, className, lineNumber, 0, null); @@ -50,7 +52,12 @@ public class Breakpoint implements IBreakpoint { Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, String condition) { this.vm = vm; this.eventHub = eventHub; - this.className = className; + if (className != null && className.contains("#")) { + this.className = className.substring(0, className.indexOf("#")); + this.methodSignature = className.substring(className.indexOf("#") + 1); + } else { + this.className = className; + } this.lineNumber = lineNumber; this.hitCount = hitCount; this.condition = condition; @@ -234,6 +241,24 @@ private static List collectLocations(List refTypes, int return locations; } + private static List collectLocations(List refTypes, String nameAndSignature) { + List locations = new ArrayList<>(); + String[] segments = nameAndSignature.split("#"); + + for (ReferenceType refType : refTypes) { + List methods = refType.methods(); + for (Method method : methods) { + if (!method.isAbstract() && !method.isNative() + && segments[0].equals(method.name()) + && (segments[1].equals(method.genericSignature()) || segments[1].equals(method.signature()))) { + locations.add(method.location()); + break; + } + } + } + return locations; + } + private List createBreakpointRequests(ReferenceType refType, int lineNumber, int hitCount, boolean includeNestedTypes) { List refTypes = new ArrayList<>(); @@ -243,7 +268,12 @@ private List createBreakpointRequests(ReferenceType refType, private List createBreakpointRequests(List refTypes, int lineNumber, int hitCount, boolean includeNestedTypes) { - List locations = collectLocations(refTypes, lineNumber, includeNestedTypes); + List locations; + if (this.methodSignature != null) { + locations = collectLocations(refTypes, this.methodSignature); + } else { + locations = collectLocations(refTypes, lineNumber, includeNestedTypes); + } // find out the existing breakpoint locations List existingLocations = new ArrayList<>(requests.size()); @@ -268,6 +298,7 @@ private List createBreakpointRequests(List ref request.addCountFilter(hitCount); } request.enable(); + request.putProperty(IBreakpoint.REQUEST_TYPE_FUNCTIONAL, Boolean.valueOf(this.methodSignature != null)); newRequests.add(request); } catch (VMDisconnectedException ex) { // enable breakpoint operation may be executing while JVM is terminating, thus the VMDisconnectedException may be 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 bb9549635..485a13739 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 @@ -14,6 +14,9 @@ import java.util.concurrent.CompletableFuture; public interface IBreakpoint extends IDebugResource { + + String REQUEST_TYPE_FUNCTIONAL = "functional"; + String className(); int getLineNumber(); 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 2bc2b5f05..2e7dbf35a 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 @@ -44,11 +44,11 @@ import com.sun.jdi.BooleanValue; import com.sun.jdi.Field; import com.sun.jdi.ObjectReference; -import com.sun.jdi.StringReference; import com.sun.jdi.ReferenceType; +import com.sun.jdi.StringReference; import com.sun.jdi.ThreadReference; -import com.sun.jdi.Value; import com.sun.jdi.VMDisconnectedException; +import com.sun.jdi.Value; import com.sun.jdi.event.BreakpointEvent; import com.sun.jdi.event.Event; import com.sun.jdi.event.StepEvent; @@ -171,6 +171,11 @@ private IBreakpoint getAssociatedEvaluatableBreakpoint(IDebugAdapterContext cont ).findFirst().orElse(null); } + private IBreakpoint getAssociatedBreakpoint(IDebugAdapterContext context, BreakpointEvent event) { + return Arrays.asList(context.getBreakpointManager().getBreakpoints()).stream() + .filter(bp -> bp.requests().contains(event.request())).findFirst().orElse(null); + } + private void registerBreakpointHandler(IDebugAdapterContext context) { IDebugSession debugSession = context.getDebugSession(); if (debugSession != null) { @@ -188,7 +193,7 @@ private void registerBreakpointHandler(IDebugAdapterContext context) { // find the breakpoint related to this breakpoint event IBreakpoint expressionBP = getAssociatedEvaluatableBreakpoint(context, (BreakpointEvent) event); - + boolean functional = (boolean) event.request().getProperty(IBreakpoint.REQUEST_TYPE_FUNCTIONAL); if (expressionBP != null) { CompletableFuture.runAsync(() -> { engine.evaluateForBreakpoint((IEvaluatableBreakpoint) expressionBP, bpThread).whenComplete((value, ex) -> { @@ -199,12 +204,14 @@ private void registerBreakpointHandler(IDebugAdapterContext context) { if (resume) { debugEvent.eventSet.resume(); } else { - context.getProtocolServer().sendEvent(new Events.StoppedEvent("breakpoint", bpThread.uniqueID())); + context.getProtocolServer().sendEvent(new Events.StoppedEvent( + functional ? "function breakpoint" : "breakpoint", bpThread.uniqueID())); } }); }); } else { - context.getProtocolServer().sendEvent(new Events.StoppedEvent("breakpoint", bpThread.uniqueID())); + context.getProtocolServer().sendEvent(new Events.StoppedEvent( + functional ? "function breakpoint" : "breakpoint", bpThread.uniqueID())); } debugEvent.shouldResume = false; } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java new file mode 100644 index 000000000..198b1eb67 --- /dev/null +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java @@ -0,0 +1,91 @@ +/******************************************************************************* +* Copyright (c) 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 +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Gayan Perera (gayanper@gmail.com) - initial API and implementation +*******************************************************************************/ +package com.microsoft.java.debug; + +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; + +@SuppressWarnings("restriction") +public class BreakpointLocationLocator + extends org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator { + + private IMethodBinding methodBinding; + + public BreakpointLocationLocator(CompilationUnit compilationUnit, int lineNumber, boolean bindingsResolved, + boolean bestMatch) { + super(compilationUnit, lineNumber, bindingsResolved, bestMatch); + } + + @Override + public boolean visit(MethodDeclaration node) { + boolean result = super.visit(node); + if (methodBinding == null && getLocationType() == LOCATION_METHOD) { + this.methodBinding = node.resolveBinding(); + } + return result; + } + + /** + * Returns the signature of method found if the + * {@link org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator#getLocationType()} + * is + * {@link org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator#LOCATION_METHOD}. + * Otherwise return null. + */ + public String getMethodSignature() { + if (this.methodBinding == null) { + return null; + } + return toSignature(this.methodBinding); + } + + /** + * Returns the name of method found if the + * {@link org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator#getLocationType()} + * is + * {@link org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator#LOCATION_METHOD}. + * Otherwise return null. + */ + public String getMethodName() { + if (this.methodBinding == null) { + return null; + } + return this.methodBinding.getName(); + } + + @Override + public String getFullyQualifiedTypeName() { + if (this.methodBinding != null) { + return this.methodBinding.getDeclaringClass().getQualifiedName(); + } + return super.getFullyQualifiedTypeName(); + } + + private String toSignature(IMethodBinding binding) { + // 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;" + String signatureString = binding.getKey(); + if (signatureString != null) { + String name = binding.getName(); + int index = signatureString.indexOf(name); + if (index > -1) { + int exceptionIndex = signatureString.indexOf("|", signatureString.lastIndexOf(")")); + if (exceptionIndex > -1) { + return signatureString.substring(index + name.length(), exceptionIndex); + } + return signatureString.substring(index + name.length()); + } + } + return null; + } +} 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 efc7ce20c..eb51faaeb 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 @@ -26,13 +26,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import com.microsoft.java.debug.core.Configuration; -import com.microsoft.java.debug.core.DebugException; -import com.microsoft.java.debug.core.adapter.AdapterUtils; -import com.microsoft.java.debug.core.adapter.Constants; -import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; -import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider; - import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.debug.core.sourcelookup.ISourceContainer; @@ -48,11 +41,18 @@ import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.internal.core.JarPackageFragmentRoot; -import org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator; import org.eclipse.jdt.launching.IVMInstall; import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.launching.LibraryLocation; +import com.microsoft.java.debug.BreakpointLocationLocator; +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugException; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.Constants; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider; + public class JdtSourceLookUpProvider implements ISourceLookUpProvider { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); private static final String JDT_SCHEME = "jdt"; @@ -162,12 +162,16 @@ public String[] getFullyQualifiedName(String uri, int[] lines, int[] columns) th // In current stage, we don't support to move the invalid breakpoint down to the next valid location, and just // mark it as "unverified". // In future, we could consider supporting to update the breakpoint to a valid location. - ValidBreakpointLocationLocator locator = new ValidBreakpointLocationLocator(astUnit, lines[i], true, true); + BreakpointLocationLocator locator = new BreakpointLocationLocator(astUnit, lines[i], true, true); astUnit.accept(locator); // When the final valid line location is same as the original line, that represents it's a valid breakpoint. // Add location type check to avoid breakpoint on method/field which will never be hit in current implementation. - if (lines[i] == locator.getLineLocation() && locator.getLocationType() == ValidBreakpointLocationLocator.LOCATION_LINE) { + if (lines[i] == locator.getLineLocation() + && locator.getLocationType() == BreakpointLocationLocator.LOCATION_LINE) { fqns[i] = locator.getFullyQualifiedTypeName(); + } else if (locator.getLocationType() == BreakpointLocationLocator.LOCATION_METHOD) { + fqns[i] = locator.getFullyQualifiedTypeName().concat("#").concat(locator.getMethodName()) + .concat("#").concat(locator.getMethodSignature()); } } }