Skip to content

Commit

Permalink
Add support for lambda breakpoints
Browse files Browse the repository at this point in the history
The support for method header breakpoints was extended to support
lambda breakpoints using the vscode inline breakpoint feature.
  • Loading branch information
gayanper committed Jul 20, 2022
1 parent 1fcbe70 commit 074ac0a
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +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));
request.putProperty(IBreakpoint.REQUEST_TYPE, computeRequestType());
newRequests.add(request);
} catch (VMDisconnectedException ex) {
// enable breakpoint operation may be executing while JVM is terminating, thus the VMDisconnectedException may be
Expand All @@ -310,6 +310,18 @@ private List<BreakpointRequest> createBreakpointRequests(List<ReferenceType> ref
return newRequests;
}

private Object computeRequestType() {
if (this.methodSignature == null) {
return IBreakpoint.REQUEST_TYPE_LINE;
}

if (this.methodSignature.startsWith("lambda$")) {
return IBreakpoint.REQUEST_TYPE_LAMBDA;
} else {
return IBreakpoint.REQUEST_TYPE_METHOD;
}
}

@Override
public void putProperty(Object key, Object value) {
propertyMap.put(key, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@

public interface IBreakpoint extends IDebugResource {

String REQUEST_TYPE_FUNCTIONAL = "functional";
String REQUEST_TYPE = "request_type";

int REQUEST_TYPE_LINE = 0;

int REQUEST_TYPE_METHOD = 1;

int REQUEST_TYPE_LAMBDA = 2;

String className();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,24 @@ public static int convertLineNumber(int line, boolean sourceLinesStartAt1, boole
}
}

/**
* Convert the source platform's column number to the target platform's column
* number.
*
* @param column
* the column number from the source platform
* @param sourceColumnsStartAt1
* the source platform's column starts at 1 or not
* @return the new column number
*/
public static int convertColumnNumber(int column, boolean sourceColumnsStartAt1) {
if (sourceColumnsStartAt1) {
return column - 1;
} else {
return column;
}
}

/**
* Convert the source platform's path format to the target platform's path format.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import com.sun.jdi.event.BreakpointEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.StepEvent;
import com.sun.jdi.request.EventRequest;

public class SetBreakpointsRequestHandler implements IDebugRequestHandler {

Expand Down Expand Up @@ -193,7 +194,8 @@ 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);
String breakpointName = computeBreakpointName(event.request());

if (expressionBP != null) {
CompletableFuture.runAsync(() -> {
engine.evaluateForBreakpoint((IEvaluatableBreakpoint) expressionBP, bpThread).whenComplete((value, ex) -> {
Expand All @@ -205,20 +207,31 @@ private void registerBreakpointHandler(IDebugAdapterContext context) {
debugEvent.eventSet.resume();
} else {
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
functional ? "function breakpoint" : "breakpoint", bpThread.uniqueID()));
breakpointName, bpThread.uniqueID()));
}
});
});
} else {
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
functional ? "function breakpoint" : "breakpoint", bpThread.uniqueID()));
breakpointName, bpThread.uniqueID()));
}
debugEvent.shouldResume = false;
}
});
}
}

private String computeBreakpointName(EventRequest request) {
switch ((int) request.getProperty(IBreakpoint.REQUEST_TYPE)) {
case IBreakpoint.REQUEST_TYPE_LAMBDA:
return "lambda breakpoint";
case IBreakpoint.REQUEST_TYPE_METHOD:
return "function breakpoint";
default:
return "breakpoint";
}
}

/**
* Check whether the condition expression is satisfied, and return a boolean value to determine to resume the thread or not.
*/
Expand Down Expand Up @@ -287,8 +300,13 @@ private IBreakpoint[] convertClientBreakpointsToDebugger(String sourceFile, Type
int[] lines = Arrays.asList(sourceBreakpoints).stream().map(sourceBreakpoint -> {
return AdapterUtils.convertLineNumber(sourceBreakpoint.line, context.isClientLinesStartAt1(), context.isDebuggerLinesStartAt1());
}).mapToInt(line -> line).toArray();

int[] columns = Arrays.asList(sourceBreakpoints).stream().map(b -> {
return AdapterUtils.convertColumnNumber(b.column, context.isClientColumnsStartAt1());
}).mapToInt(b -> b).toArray();

ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class);
String[] fqns = sourceProvider.getFullyQualifiedName(sourceFile, lines, null);
String[] fqns = sourceProvider.getFullyQualifiedName(sourceFile, lines, columns);
IBreakpoint[] breakpoints = new IBreakpoint[lines.length];
for (int i = 0; i < lines.length; i++) {
int hitCount = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ public Breakpoint(int id, boolean verified, int line, String message) {

public static class SourceBreakpoint {
public int line;
public int column;
public String hitCondition;
public String condition;
public String logMessage;
Expand All @@ -217,6 +218,16 @@ public SourceBreakpoint(int line, String condition, String hitCondition) {
this.condition = condition;
this.hitCondition = hitCondition;
}

/**
* Constructor.
*/
public SourceBreakpoint(int line, String condition, String hitCondition, int column) {
this.line = line;
this.column = column;
this.condition = condition;
this.hitCondition = hitCondition;
}
}

public static class FunctionBreakpoint {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public class BreakpointLocationLocator

private IMethodBinding methodBinding;

public BreakpointLocationLocator(CompilationUnit compilationUnit, int lineNumber, boolean bindingsResolved,
public BreakpointLocationLocator(CompilationUnit compilationUnit, int lineNumber,
boolean bindingsResolved,
boolean bestMatch) {
super(compilationUnit, lineNumber, bindingsResolved, bestMatch);
}
Expand All @@ -45,7 +46,7 @@ public String getMethodSignature() {
if (this.methodBinding == null) {
return null;
}
return toSignature(this.methodBinding);
return toSignature(this.methodBinding, getMethodName());
}

/**
Expand All @@ -70,13 +71,12 @@ public String getFullyQualifiedTypeName() {
return super.getFullyQualifiedTypeName();
}

private String toSignature(IMethodBinding binding) {
static String toSignature(IMethodBinding binding, String name) {
// 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(")"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.microsoft.java.debug;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.LambdaExpression;

public class LambdaExpressionLocator extends ASTVisitor {
private CompilationUnit compilationUnit;
private int line;
private int column;
private boolean found;

private IMethodBinding lambdaMethodBinding;
private LambdaExpression lambdaExpression;

public LambdaExpressionLocator(CompilationUnit compilationUnit, int line, int column) {
this.compilationUnit = compilationUnit;
this.line = line;
this.column = column;
}

@Override
public boolean visit(LambdaExpression node) {
if (column > -1) {
int startPosition = node.getStartPosition();

int columnNumber = this.compilationUnit.getColumnNumber(startPosition);
int lineNumber = this.compilationUnit.getLineNumber(startPosition);

if (column == columnNumber && lineNumber == line) {
this.lambdaMethodBinding = node.resolveMethodBinding();
this.found = true;
this.lambdaExpression = node;
return false;
}
}
return super.visit(node);
}

/**
* Returns <code>true</code> if a lambda is found at given location.
*/
public boolean isFound() {
return found;
}

/**
* Returns the signature of lambda method otherwise return null.
*/
public String getMethodSignature() {
if (!this.found) {
return null;
}
return BreakpointLocationLocator.toSignature(this.lambdaMethodBinding, getMethodName());
}

/**
* Returns the name of lambda method otherwise return null.
*/
public String getMethodName() {
if (!this.found) {
return null;
}
String key = this.lambdaMethodBinding.getKey();
return key.substring(key.indexOf('.') + 1, key.indexOf('('));
}

/**
* Returns the name of the type which the lambda method is found.
*/
public String getFullyQualifiedTypeName() {
if (this.found) {
ASTNode parent = lambdaExpression.getParent();
while (parent != null) {
if (parent instanceof AbstractTypeDeclaration) {
AbstractTypeDeclaration declaration = (AbstractTypeDeclaration) parent;
return declaration.resolveBinding().getBinaryName();
}
parent = parent.getParent();
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.eclipse.jdt.launching.LibraryLocation;

import com.microsoft.java.debug.BreakpointLocationLocator;
import com.microsoft.java.debug.LambdaExpressionLocator;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
Expand Down Expand Up @@ -155,23 +156,42 @@ public String[] getFullyQualifiedName(String uri, int[] lines, int[] columns) th
String[] fqns = new String[lines.length];
if (astUnit != null) {
for (int i = 0; i < lines.length; i++) {
// TODO
// The ValidBreakpointLocationLocator will verify if the current line is a valid location or not.
// If so, it will return the fully qualified name of the class type that contains the current line.
// Otherwise, it will try to find a valid location from the next lines and return it's fully qualified name.
// 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.
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() == 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());
// if we have a column, try to find the lambda expression at that column
LambdaExpressionLocator lambdaExpressionLocator = new LambdaExpressionLocator(astUnit, lines[i],
columns[i]);
astUnit.accept(lambdaExpressionLocator);
if (lambdaExpressionLocator.isFound()) {
fqns[i] = lambdaExpressionLocator.getFullyQualifiedTypeName().concat("#")
.concat(lambdaExpressionLocator.getMethodName())
.concat("#").concat(lambdaExpressionLocator.getMethodSignature());
} else {
// TODO
// The ValidBreakpointLocationLocator will verify if the current line is a valid
// location or not.
// If so, it will return the fully qualified name of the class type that
// contains the current line.
// Otherwise, it will try to find a valid location from the next lines and
// return it's fully qualified name.
// 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.
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() == 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 074ac0a

Please sign in to comment.