Skip to content

Commit

Permalink
Improve support for method breakpoints (#426)
Browse files Browse the repository at this point in the history
Now method breakpoints can added by adding a line breakpoint at method
header.
  • Loading branch information
gayanper authored Jul 19, 2022
1 parent 2461b03 commit 1fcbe70
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,6 +39,7 @@ public class Breakpoint implements IBreakpoint {
private String condition = null;
private String logMessage = null;
private HashMap<Object, Object> propertyMap = new HashMap<>();
private String methodSignature = null;

Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber) {
this(vm, eventHub, className, lineNumber, 0, null);
Expand All @@ -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;
Expand Down Expand Up @@ -234,6 +241,24 @@ private static List<Location> collectLocations(List<ReferenceType> refTypes, int
return locations;
}

private static List<Location> collectLocations(List<ReferenceType> refTypes, String nameAndSignature) {
List<Location> locations = new ArrayList<>();
String[] segments = nameAndSignature.split("#");

for (ReferenceType refType : refTypes) {
List<Method> 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<BreakpointRequest> createBreakpointRequests(ReferenceType refType, int lineNumber, int hitCount,
boolean includeNestedTypes) {
List<ReferenceType> refTypes = new ArrayList<>();
Expand All @@ -243,7 +268,12 @@ private List<BreakpointRequest> createBreakpointRequests(ReferenceType refType,

private List<BreakpointRequest> createBreakpointRequests(List<ReferenceType> refTypes, int lineNumber,
int hitCount, boolean includeNestedTypes) {
List<Location> locations = collectLocations(refTypes, lineNumber, includeNestedTypes);
List<Location> locations;
if (this.methodSignature != null) {
locations = collectLocations(refTypes, this.methodSignature);
} else {
locations = collectLocations(refTypes, lineNumber, includeNestedTypes);
}

// find out the existing breakpoint locations
List<Location> existingLocations = new ArrayList<>(requests.size());
Expand All @@ -268,6 +298,7 @@ private List<BreakpointRequest> createBreakpointRequests(List<ReferenceType> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
import java.util.concurrent.CompletableFuture;

public interface IBreakpoint extends IDebugResource {

String REQUEST_TYPE_FUNCTIONAL = "functional";

String className();

int getLineNumber();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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) -> {
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 ([email protected]) - 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<T:Ljava/lang/Object;>([TT;)Ljava/util/List<TT;>;"
// "([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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";
Expand Down Expand Up @@ -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());
}
}
}
Expand Down

0 comments on commit 1fcbe70

Please sign in to comment.