Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for lambda breakpoints #427

Merged
merged 5 commits into from
Aug 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,19 @@ public String getCondition() {

@Override
public boolean equals(Object obj) {
if (!(obj instanceof IBreakpoint)) {
if (!(obj instanceof Breakpoint)) {
return super.equals(obj);
}

IBreakpoint breakpoint = (IBreakpoint) obj;
return Objects.equals(this.className(), breakpoint.className()) && this.getLineNumber() == breakpoint.getLineNumber();
Breakpoint breakpoint = (Breakpoint) obj;
return Objects.equals(this.className(), breakpoint.className())
&& this.getLineNumber() == breakpoint.getLineNumber()
&& Objects.equals(this.methodSignature, breakpoint.methodSignature);
}

@Override
public int hashCode() {
return Objects.hash(this.className, this.lineNumber, this.methodSignature);
}

@Override
Expand Down Expand Up @@ -298,7 +305,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 +317,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 @@ -11,15 +11,15 @@

package com.microsoft.java.debug.core;

import org.apache.commons.lang3.StringUtils;

import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;

import com.sun.jdi.event.ThreadDeathEvent;
import org.apache.commons.lang3.StringUtils;

import com.sun.jdi.ThreadReference;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.event.ThreadDeathEvent;

import io.reactivex.disposables.Disposable;

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 @@ -79,12 +79,12 @@ public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, bo

// Compute the breakpoints that are newly added.
List<IBreakpoint> toAdd = new ArrayList<>();
List<Integer> visitedLineNumbers = new ArrayList<>();
List<Integer> visitedBreakpoints = new ArrayList<>();
for (IBreakpoint breakpoint : breakpoints) {
IBreakpoint existed = breakpointMap.get(String.valueOf(breakpoint.getLineNumber()));
IBreakpoint existed = breakpointMap.get(String.valueOf(breakpoint.hashCode()));
if (existed != null) {
result.add(existed);
visitedLineNumbers.add(existed.getLineNumber());
visitedBreakpoints.add(existed.hashCode());
continue;
} else {
result.add(breakpoint);
Expand All @@ -95,7 +95,7 @@ public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, bo
// Compute the breakpoints that are no longer listed.
List<IBreakpoint> toRemove = new ArrayList<>();
for (IBreakpoint breakpoint : breakpointMap.values()) {
if (!visitedLineNumbers.contains(breakpoint.getLineNumber())) {
if (!visitedBreakpoints.contains(breakpoint.hashCode())) {
toRemove.add(breakpoint);
}
}
Expand All @@ -113,7 +113,7 @@ private void addBreakpointsInternally(String source, IBreakpoint[] breakpoints)
for (IBreakpoint breakpoint : breakpoints) {
breakpoint.putProperty("id", this.nextBreakpointId.getAndIncrement());
this.breakpoints.add(breakpoint);
breakpointMap.put(String.valueOf(breakpoint.getLineNumber()), breakpoint);
breakpointMap.put(String.valueOf(breakpoint.hashCode()), breakpoint);
}
}
}
Expand All @@ -133,7 +133,7 @@ private void removeBreakpointsInternally(String source, IBreakpoint[] breakpoint
// Destroy the breakpoint on the debugee VM.
breakpoint.close();
this.breakpoints.remove(breakpoint);
breakpointMap.remove(String.valueOf(breakpoint.getLineNumber()));
breakpointMap.remove(String.valueOf(breakpoint.hashCode()));
} catch (Exception e) {
logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e);
}
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,105 @@
/*******************************************************************************
* 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.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) {
// we only support inline breakpoints which are added before the expression part of the
// lambda. And we don't support lambda blocks since they can be debugged using line
// breakpoints.
if (column > -1) {
int startPosition = node.getStartPosition();
int endPosition = node.getBody().getStartPosition();
int offset = this.compilationUnit.getPosition(line, column);
// lambda on same line:
// list.stream().map(i -> i + 1);
//
// lambda on multiple lines:
// list.stream().map(user
// -> user.isSystem() ? new SystemUser(user) : new EndUser(user));
testforstephen marked this conversation as resolved.
Show resolved Hide resolved

if (offset >= startPosition && offset <= endPosition) {
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;
}
}
Loading