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

[Debugger] Make the breakpoint verification configurable #43693

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 @@ -35,8 +35,8 @@
import java.util.Map;
import java.util.Optional;

import static org.ballerinalang.debugadapter.JBallerinaDebugServer.isBalStackFrame;
import static org.ballerinalang.debugadapter.evaluation.utils.EvaluationUtils.STRAND_VAR_NAME;
import static org.ballerinalang.debugadapter.utils.ServerUtils.isBalStackFrame;
import static org.ballerinalang.debugadapter.variable.VariableUtils.isService;
import static org.ballerinalang.debugadapter.variable.VariableUtils.removeRedundantQuotes;
import static org.wso2.ballerinalang.compiler.parser.BLangAnonymousModelHelper.LAMBDA;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import java.util.concurrent.TimeoutException;

import static org.ballerinalang.debugadapter.utils.PackageUtils.getQualifiedClassName;
import static org.ballerinalang.debugadapter.utils.ServerUtils.supportsBreakpointVerification;

/**
* Implementation of Ballerina breakpoint processor. The existing implementation is capable of processing advanced
Expand Down Expand Up @@ -202,7 +203,7 @@ void activateUserBreakPoints(ReferenceType referenceType, boolean shouldNotify)
bpReq.enable();

// verifies the breakpoint reachability and notifies the client if required.
if (!breakpoint.isVerified()) {
if (supportsBreakpointVerification(context) && !breakpoint.isVerified()) {
breakpoint.setVerified(true);
if (shouldNotify) {
notifyBreakPointChangesToClient(breakpoint);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.ballerinalang.debugadapter.runner.BProgramRunner;
import org.ballerinalang.debugadapter.runner.BSingleFileRunner;
import org.ballerinalang.debugadapter.utils.PackageUtils;
import org.ballerinalang.debugadapter.utils.ServerUtils;
import org.ballerinalang.debugadapter.variable.BCompoundVariable;
import org.ballerinalang.debugadapter.variable.BSimpleVariable;
import org.ballerinalang.debugadapter.variable.BVariable;
Expand Down Expand Up @@ -83,7 +84,6 @@
import org.eclipse.lsp4j.debug.SetBreakpointsArguments;
import org.eclipse.lsp4j.debug.SetBreakpointsResponse;
import org.eclipse.lsp4j.debug.SetExceptionBreakpointsArguments;
import org.eclipse.lsp4j.debug.Source;
import org.eclipse.lsp4j.debug.SourceArguments;
import org.eclipse.lsp4j.debug.SourceBreakpoint;
import org.eclipse.lsp4j.debug.SourceResponse;
Expand Down Expand Up @@ -132,10 +132,13 @@
import static org.ballerinalang.debugadapter.completion.util.CompletionUtil.getTriggerCharacters;
import static org.ballerinalang.debugadapter.completion.util.CompletionUtil.getVisibleSymbolCompletions;
import static org.ballerinalang.debugadapter.completion.util.CompletionUtil.triggerCharactersFound;
import static org.ballerinalang.debugadapter.utils.PackageUtils.BAL_FILE_EXT;
import static org.ballerinalang.debugadapter.utils.PackageUtils.GENERATED_VAR_PREFIX;
import static org.ballerinalang.debugadapter.utils.PackageUtils.INIT_CLASS_NAME;
import static org.ballerinalang.debugadapter.utils.PackageUtils.getQualifiedClassName;
import static org.ballerinalang.debugadapter.utils.ServerUtils.isBalStackFrame;
import static org.ballerinalang.debugadapter.utils.ServerUtils.isBalStrand;
import static org.ballerinalang.debugadapter.utils.ServerUtils.isNoDebugMode;
import static org.ballerinalang.debugadapter.utils.ServerUtils.toBalBreakpoint;

/**
* JBallerina debug server implementation.
Expand Down Expand Up @@ -178,7 +181,7 @@ public ExecutionContext getContext() {
return context;
}

ClientConfigHolder getClientConfigHolder() {
public ClientConfigHolder getClientConfigHolder() {
return clientConfigHolder;
}

Expand Down Expand Up @@ -221,12 +224,13 @@ public CompletableFuture<Capabilities> initialize(InitializeRequestArguments arg
public CompletableFuture<SetBreakpointsResponse> setBreakpoints(SetBreakpointsArguments args) {
return CompletableFuture.supplyAsync(() -> {
SetBreakpointsResponse bpResponse = new SetBreakpointsResponse();
if (isNoDebugMode()) {
if (isNoDebugMode(context)) {
return bpResponse;
}

BalBreakpoint[] balBreakpoints = Arrays.stream(args.getBreakpoints())
.map((SourceBreakpoint sourceBreakpoint) -> toBalBreakpoint(sourceBreakpoint, args.getSource()))
.map((SourceBreakpoint sourceBreakpoint) -> toBalBreakpoint(context, sourceBreakpoint,
args.getSource()))
.toArray(BalBreakpoint[]::new);

LinkedHashMap<Integer, BalBreakpoint> breakpointsMap = new LinkedHashMap<>();
Expand Down Expand Up @@ -345,7 +349,7 @@ public CompletableFuture<StackTraceResponse> stackTrace(StackTraceArguments args
} else {
StackFrame[] validFrames = activeThread.frames().stream()
.map(this::toDapStackFrame)
.filter(JBallerinaDebugServer::isValidFrame)
.filter(ServerUtils::isValidFrame)
.toArray(StackFrame[]::new);
stackTraceResponse.setStackFrames(validFrames);
threadStackTraces.put(activeThread.uniqueID(), validFrames);
Expand Down Expand Up @@ -785,13 +789,6 @@ private StackFrame toDapStackFrame(StackFrameProxyImpl stackFrameProxy) {
}
}

private BalBreakpoint toBalBreakpoint(SourceBreakpoint sourceBreakpoint, Source source) {
BalBreakpoint breakpoint = new BalBreakpoint(source, sourceBreakpoint.getLine());
breakpoint.setCondition(sourceBreakpoint.getCondition());
breakpoint.setLogMessage(sourceBreakpoint.getLogMessage());
return breakpoint;
}

/**
* Returns a map of all currently running threads in the remote VM, against their unique ID.
* <p>
Expand Down Expand Up @@ -844,47 +841,6 @@ && isBalStrand(threadReference)
return balStrandThreads;
}

/**
* Validates whether the given DAP thread reference represents a ballerina strand.
* <p>
*
* @param threadReference DAP thread reference
* @return true if the given DAP thread reference represents a ballerina strand.
*/
private static boolean isBalStrand(ThreadReference threadReference) {
// Todo - Refactor to use thread proxy implementation
try {
return isBalStackFrame(threadReference.frames().get(0));
} catch (Exception e) {
return false;
}
}

/**
* Validates whether the given DAP stack frame represents a ballerina call stack frame.
*
* @param frame DAP stack frame
* @return true if the given DAP stack frame represents a ballerina call stack frame.
*/
static boolean isBalStackFrame(com.sun.jdi.StackFrame frame) {
// Todo - Refactor to use stack frame proxy implementation
try {
return frame.location().sourceName().endsWith(BAL_FILE_EXT);
} catch (Exception e) {
return false;
}
}

/**
* Validates a given ballerina stack frame for its source information.
*
* @param stackFrame ballerina stack frame
* @return true if its a valid ballerina frame
*/
static boolean isValidFrame(StackFrame stackFrame) {
return stackFrame != null && stackFrame.getSource() != null && stackFrame.getLine() > 0;
}

/**
* Asynchronously listens to remote debuggee stdout + error streams and redirects the output to the client debug
* console.
Expand Down Expand Up @@ -978,11 +934,6 @@ private void prepareFor(DebugInstruction instruction, int threadId) {
context.setPrevInstruction(instruction);
}

private boolean isNoDebugMode() {
ClientConfigHolder confHolder = context.getAdapter().getClientConfigHolder();
return confHolder instanceof ClientLaunchConfigHolder launchConfigHolder && launchConfigHolder.isNoDebugMode();
}

private Variable[] computeGlobalScopeVariables(VariablesArguments requestArgs) {
int stackFrameReference = requestArgs.getVariablesReference();
String classQName = PackageUtils.getQualifiedClassName(suspendedContext, INIT_CLASS_NAME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.ballerinalang.debugadapter.breakpoint.BalBreakpoint;
import org.ballerinalang.debugadapter.jdi.StackFrameProxyImpl;
import org.ballerinalang.debugadapter.jdi.ThreadReferenceProxyImpl;
import org.ballerinalang.debugadapter.utils.ServerUtils;
import org.eclipse.lsp4j.debug.ContinuedEventArguments;
import org.eclipse.lsp4j.debug.StoppedEventArguments;
import org.eclipse.lsp4j.debug.StoppedEventArgumentsReason;
Expand All @@ -48,8 +49,8 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;

import static org.ballerinalang.debugadapter.JBallerinaDebugServer.isBalStackFrame;
import static org.ballerinalang.debugadapter.utils.PackageUtils.BAL_FILE_EXT;
import static org.ballerinalang.debugadapter.utils.ServerUtils.isBalStackFrame;

/**
* JDI Event processor implementation.
Expand Down Expand Up @@ -226,7 +227,7 @@ List<BallerinaStackFrame> filterValidBallerinaFrames(List<StackFrameProxyImpl> j
if (balStackFrame.getAsDAPStackFrame().isEmpty()) {
continue;
}
if (JBallerinaDebugServer.isValidFrame(balStackFrame.getAsDAPStackFrame().get())) {
if (ServerUtils.isValidFrame(balStackFrame.getAsDAPStackFrame().get())) {
validFrames.add(balStackFrame);
}
} catch (Exception ignored) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
private String condition;
private LogMessage logMessage;
private boolean isVerified;
private boolean supportsVerification;

private static final AtomicInteger nextID = new AtomicInteger(0);

Expand All @@ -48,6 +49,7 @@
this.source = source;
this.line = line;
this.isVerified = false;
this.supportsVerification = false;
}

public Integer getLine() {
Expand Down Expand Up @@ -91,11 +93,15 @@
breakpoint.setId(id);
breakpoint.setLine(line);
breakpoint.setSource(source);
breakpoint.setVerified(isVerified);
breakpoint.setVerified(!supportsVerification || isVerified);
return breakpoint;
}

private boolean isTemplate(String logMessage) {
return logMessage != null && Pattern.compile(INTERPOLATION_REGEX).matcher(logMessage).find();
}

public void setSupportsVerification(boolean supportsVerification) {
this.supportsVerification = supportsVerification;
}

Check warning on line 106 in misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/breakpoint/BalBreakpoint.java

View check run for this annotation

Codecov / codecov/patch

misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/breakpoint/BalBreakpoint.java#L105-L106

Added lines #L105 - L106 were not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
protected static final String ARG_DEBUGGEE_PORT = "debuggeePort";
private static final String ARG_CAPABILITIES = "capabilities";
private static final String ARG_SUPPORT_READONLY_EDITOR = "supportsReadOnlyEditors";
private static final String ARG_SUPPORT_BP_VERIFICATION = "supportsBreakpointVerification";
private static final String ARG_TERMINAL_KIND = "terminal";
private static final String INTEGRATED_TERMINAL_KIND = "INTEGRATED";
private static final String EXTERNAL_TERMINAL_KIND = "EXTERNAL";
Expand Down Expand Up @@ -88,6 +89,15 @@
extendedClientCapabilities.setSupportsReadOnlyEditors(false);
}

Object bpVerificationConfig = capabilities.get(ARG_SUPPORT_BP_VERIFICATION);
if (bpVerificationConfig instanceof Boolean b) {
extendedClientCapabilities.setSupportsBreakpointVerification(b);
} else if (bpVerificationConfig instanceof String s) {
extendedClientCapabilities.setSupportsBreakpointVerification(Boolean.parseBoolean(s));

Check warning on line 96 in misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java

View check run for this annotation

Codecov / codecov/patch

misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java#L96

Added line #L96 was not covered by tests
} else {
extendedClientCapabilities.setSupportsBreakpointVerification(false);
}

return Optional.ofNullable(extendedClientCapabilities);
}

Expand Down Expand Up @@ -122,6 +132,7 @@
public static class ExtendedClientCapabilities {

private boolean supportsReadOnlyEditors = false;
private boolean supportsBreakpointVerification = false;

public boolean supportsReadOnlyEditors() {
return supportsReadOnlyEditors;
Expand All @@ -130,5 +141,13 @@
public void setSupportsReadOnlyEditors(boolean supportsReadOnlyEditors) {
this.supportsReadOnlyEditors = supportsReadOnlyEditors;
}

public boolean supportsBreakpointVerification() {
return supportsBreakpointVerification;
}

public void setSupportsBreakpointVerification(boolean supportsBreakpointVerification) {
this.supportsBreakpointVerification = supportsBreakpointVerification;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://wso2.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ballerinalang.debugadapter.utils;

import com.sun.jdi.ThreadReference;
import org.ballerinalang.debugadapter.ExecutionContext;
import org.ballerinalang.debugadapter.breakpoint.BalBreakpoint;
import org.ballerinalang.debugadapter.config.ClientConfigHolder;
import org.ballerinalang.debugadapter.config.ClientLaunchConfigHolder;
import org.eclipse.lsp4j.debug.Source;
import org.eclipse.lsp4j.debug.SourceBreakpoint;
import org.eclipse.lsp4j.debug.StackFrame;

import java.util.Objects;

import static org.ballerinalang.debugadapter.utils.PackageUtils.BAL_FILE_EXT;

/**
* Ballerina debug server related utility functions.
*
* @since 2201.11.0
*/
public class ServerUtils {

Check warning on line 36 in misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java

View check run for this annotation

Codecov / codecov/patch

misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java#L36

Added line #L36 was not covered by tests

/**
* Checks whether the debug server should run in no-debug mode.
*
* @param context debug context
* @return true if the debug mode is no-debug mode
*/
public static boolean isNoDebugMode(ExecutionContext context) {
ClientConfigHolder confHolder = context.getAdapter().getClientConfigHolder();
return confHolder instanceof ClientLaunchConfigHolder launchConfigHolder && launchConfigHolder.isNoDebugMode();
}

/**
* Validates whether the given DAP thread reference represents a ballerina strand.
*
* @param threadReference DAP thread reference
* @return true if the given DAP thread reference represents a ballerina strand
*/
public static boolean isBalStrand(ThreadReference threadReference) {
// Todo - Refactor to use thread proxy implementation
try {
return isBalStackFrame(threadReference.frames().getFirst());
} catch (Exception e) {
return false;
}
}

/**
* Validates whether the given DAP stack frame represents a ballerina call stack frame.
*
* @param frame DAP stack frame
* @return true if the given DAP stack frame represents a ballerina call stack frame
*/
public static boolean isBalStackFrame(com.sun.jdi.StackFrame frame) {
// Todo - Refactor to use stack frame proxy implementation
try {
return frame.location().sourceName().endsWith(BAL_FILE_EXT);
} catch (Exception e) {
return false;
}
}

/**
* Validates a given ballerina stack frame for its source information.
*
* @param stackFrame ballerina stack frame
* @return true if it's a valid ballerina frame
*/
public static boolean isValidFrame(StackFrame stackFrame) {
return stackFrame != null && stackFrame.getSource() != null && stackFrame.getLine() > 0;
}

/**
* Converts a given DAP source breakpoint instance to a ballerina breakpoint instance.
*
* @param context debug context
* @param sourceBp source breakpoint
* @param source source
* @return ballerina breakpoint
*/
public static BalBreakpoint toBalBreakpoint(ExecutionContext context, SourceBreakpoint sourceBp, Source source) {
BalBreakpoint breakpoint = new BalBreakpoint(source, sourceBp.getLine());
breakpoint.setCondition(sourceBp.getCondition());
breakpoint.setLogMessage(sourceBp.getLogMessage());
// If the debug client doesn't support breakpoint verification, mark the breakpoint as verified by default.
if (supportsBreakpointVerification(context)) {
breakpoint.setSupportsVerification(true);

Check warning on line 103 in misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java

View check run for this annotation

Codecov / codecov/patch

misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java#L103

Added line #L103 was not covered by tests
}

return breakpoint;
}

/**
* Checks whether the connected debug client supports breakpoint verification.
*
* @param context debug context
* @return true if the connected debug client supports breakpoint verification
*/
public static boolean supportsBreakpointVerification(ExecutionContext context) {
ClientConfigHolder configHolder = context.getAdapter().getClientConfigHolder();

return Objects.nonNull(configHolder) && configHolder.getExtendedCapabilities()
.map(ClientConfigHolder.ExtendedClientCapabilities::supportsBreakpointVerification)
.orElse(false);
}
}
Loading
Loading