From 438078d1818d14cd0bf1de8b5c10a8c99f18c16b Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Wed, 20 Jul 2022 15:19:36 +0200 Subject: [PATCH 1/5] Add support for lambda breakpoints The support for method header breakpoints was extended to support lambda breakpoints using the vscode inline breakpoint feature. --- .../microsoft/java/debug/core/Breakpoint.java | 14 ++- .../java/debug/core/IBreakpoint.java | 8 +- .../java/debug/core/adapter/AdapterUtils.java | 18 ++++ .../handler/SetBreakpointsRequestHandler.java | 26 ++++- .../java/debug/core/protocol/Types.java | 11 +++ .../java/debug/BreakpointLocationLocator.java | 8 +- .../java/debug/LambdaExpressionLocator.java | 97 +++++++++++++++++++ .../internal/JdtSourceLookUpProvider.java | 54 +++++++---- 8 files changed, 209 insertions(+), 27 deletions(-) create mode 100644 com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java 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 3a8d88316..28ae0b396 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 @@ -298,7 +298,7 @@ private List createBreakpointRequests(List 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 @@ -310,6 +310,18 @@ private List createBreakpointRequests(List 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); 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 485a13739..3db16b577 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 @@ -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(); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java index 74d3fb292..5c1272102 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java @@ -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. * 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 2e7dbf35a..6dc74a0bd 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 @@ -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 { @@ -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) -> { @@ -205,13 +207,13 @@ 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; } @@ -219,6 +221,17 @@ private void registerBreakpointHandler(IDebugAdapterContext context) { } } + 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. */ @@ -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; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java index cd20faf4f..979c7d632 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java @@ -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; @@ -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 { 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 index 198b1eb67..00cffb766 100644 --- 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 @@ -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); } @@ -45,7 +46,7 @@ public String getMethodSignature() { if (this.methodBinding == null) { return null; } - return toSignature(this.methodBinding); + return toSignature(this.methodBinding, getMethodName()); } /** @@ -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([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(")")); diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java new file mode 100644 index 000000000..b60328240 --- /dev/null +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java @@ -0,0 +1,97 @@ +/******************************************************************************* +* 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.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 true 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; + } +} 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 eb51faaeb..81f9f09f9 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 @@ -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; @@ -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()); + } } } } From 9b001d786fd7ff42555cd29fb7307e95e6692aae Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Fri, 22 Jul 2022 19:30:56 +0200 Subject: [PATCH 2/5] Optimize lambda search Only search for lambda when the breakpoing is an inline breakpoint. Add support for any column position within lambda expression. --- .../java/debug/LambdaExpressionLocator.java | 5 +- .../internal/JdtSourceLookUpProvider.java | 73 ++++++++++--------- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java index b60328240..65b4c2faa 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java @@ -37,10 +37,11 @@ public boolean visit(LambdaExpression node) { if (column > -1) { int startPosition = node.getStartPosition(); - int columnNumber = this.compilationUnit.getColumnNumber(startPosition); + int startColumn = this.compilationUnit.getColumnNumber(startPosition); + int endColumn = this.compilationUnit.getColumnNumber(startPosition + node.getLength()); int lineNumber = this.compilationUnit.getLineNumber(startPosition); - if (column == columnNumber && lineNumber == line) { + if (column >= startColumn && column <= endColumn && lineNumber == line) { this.lambdaMethodBinding = node.resolveMethodBinding(); this.found = true; this.lambdaExpression = node; 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 81f9f09f9..1dc1c0e64 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 @@ -156,43 +156,46 @@ 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++) { - // 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()); + if (columns[i] > -1) { + // 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()); + continue; } } + + // 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()); + } } } return fqns; From ec133c3484984d4afe647e84db414d0cfdc6fa6d Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Tue, 26 Jul 2022 21:34:48 +0200 Subject: [PATCH 3/5] Improve for multi line and multi lambdas --- .../microsoft/java/debug/core/Breakpoint.java | 13 ++++++++++--- .../java/debug/core/EvaluatableBreakpoint.java | 6 +++--- .../debug/core/adapter/BreakpointManager.java | 12 ++++++------ .../java/debug/LambdaExpressionLocator.java | 17 ++++++++++++++--- 4 files changed, 33 insertions(+), 15 deletions(-) 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 28ae0b396..a81968ad3 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 @@ -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 diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/EvaluatableBreakpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/EvaluatableBreakpoint.java index 1a0647411..9b3fdb2dd 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/EvaluatableBreakpoint.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/EvaluatableBreakpoint.java @@ -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; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java index 4b0a7ae89..eaf1bb56f 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java @@ -79,12 +79,12 @@ public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, bo // Compute the breakpoints that are newly added. List toAdd = new ArrayList<>(); - List visitedLineNumbers = new ArrayList<>(); + List 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); @@ -95,7 +95,7 @@ public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, bo // Compute the breakpoints that are no longer listed. List toRemove = new ArrayList<>(); for (IBreakpoint breakpoint : breakpointMap.values()) { - if (!visitedLineNumbers.contains(breakpoint.getLineNumber())) { + if (!visitedBreakpoints.contains(breakpoint.hashCode())) { toRemove.add(breakpoint); } } @@ -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); } } } @@ -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); } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java index 65b4c2faa..364ddb1ec 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java @@ -38,10 +38,21 @@ public boolean visit(LambdaExpression node) { int startPosition = node.getStartPosition(); int startColumn = this.compilationUnit.getColumnNumber(startPosition); - int endColumn = this.compilationUnit.getColumnNumber(startPosition + node.getLength()); - int lineNumber = this.compilationUnit.getLineNumber(startPosition); + int endPosition = startPosition + node.getLength(); + int endColumn = this.compilationUnit.getColumnNumber(endPosition); + int startLine = this.compilationUnit.getLineNumber(startPosition); + int endLine = this.compilationUnit.getLineNumber(endPosition); - if (column >= startColumn && column <= endColumn && lineNumber == line) { + // 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)); + + if ((startLine == endLine && column >= startColumn && column <= endColumn && line == startLine) + || (startLine != endLine && line >= startLine && line <= endLine + && (column >= startColumn || column <= endColumn))) { this.lambdaMethodBinding = node.resolveMethodBinding(); this.found = true; this.lambdaExpression = node; From 6dcb88d162b50d0072a60a1f684c2358a8d68616 Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Wed, 3 Aug 2022 19:28:49 +0200 Subject: [PATCH 4/5] Improve lambda breakpoints - Only support for expressions. - The inline breakpoint should be before the expression start. --- .../java/debug/LambdaExpressionLocator.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java index 364ddb1ec..747ae8bba 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java @@ -34,15 +34,13 @@ public LambdaExpressionLocator(CompilationUnit compilationUnit, int line, int co @Override public boolean visit(LambdaExpression node) { - if (column > -1) { + // 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 && node.getNodeType() != ASTNode.BLOCK) { int startPosition = node.getStartPosition(); - - int startColumn = this.compilationUnit.getColumnNumber(startPosition); - int endPosition = startPosition + node.getLength(); - int endColumn = this.compilationUnit.getColumnNumber(endPosition); - int startLine = this.compilationUnit.getLineNumber(startPosition); - int endLine = this.compilationUnit.getLineNumber(endPosition); - + int endPosition = node.getBody().getStartPosition(); + int offset = this.compilationUnit.getPosition(line, column); // lambda on same line: // list.stream().map(i -> i + 1); // @@ -50,9 +48,7 @@ public boolean visit(LambdaExpression node) { // list.stream().map(user // -> user.isSystem() ? new SystemUser(user) : new EndUser(user)); - if ((startLine == endLine && column >= startColumn && column <= endColumn && line == startLine) - || (startLine != endLine && line >= startLine && line <= endLine - && (column >= startColumn || column <= endColumn))) { + if (offset >= startPosition && offset <= endPosition) { this.lambdaMethodBinding = node.resolveMethodBinding(); this.found = true; this.lambdaExpression = node; From aec3abe8c58d4e2d15286be335ab77e8c3fb021b Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Thu, 4 Aug 2022 09:55:20 +0800 Subject: [PATCH 5/5] Remove the unnecessary nodeType check Since LambdaExpression.getNodeType() returns `ASTNode.LAMBDA_EXPRESSION`, the condition `node.getNodeType() != ASTNode.BLOCK` is always true and i just remove it. --- .../java/com/microsoft/java/debug/LambdaExpressionLocator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java index 747ae8bba..351be8cb3 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/LambdaExpressionLocator.java @@ -37,7 +37,7 @@ 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 && node.getNodeType() != ASTNode.BLOCK) { + if (column > -1) { int startPosition = node.getStartPosition(); int endPosition = node.getBody().getStartPosition(); int offset = this.compilationUnit.getPosition(line, column);