From a310f55e1c49b9e1834a4ce7985080bd5a64a0d3 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Wed, 25 Dec 2024 09:51:07 +0530 Subject: [PATCH 1/2] Disable stepping into external sources in low-code mode --- .../debugadapter/JBallerinaDebugServer.java | 4 +- .../debugadapter/JDIEventProcessor.java | 52 ++++++++++++++++++- .../config/ClientConfigHolder.java | 17 ++++++ .../debugadapter/utils/PackageUtils.java | 2 +- 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index 3ab74fd6d4ff..51d39ec8268f 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -328,7 +328,7 @@ public CompletableFuture pause(PauseArguments args) { // Checks if the program VM is a read-only VM. If a method which modified the state of the VM is called // on a read-only VM, a `VMCannotBeModifiedException` will be thrown. if (!debuggeeVM.canBeModified()) { - getOutputLogger().sendConsoleOutput("Failed to suspend the remote VM due to: pause requests are not " + + getOutputLogger().sendDebugServerOutput("Failed to suspend the remote VM due to: pause requests are not " + "supported on read-only VMs"); return CompletableFuture.completedFuture(null); } @@ -679,7 +679,7 @@ private void launchInTerminal(BProgramRunner programRunner) throws ClientConfigu programRunner.getBallerinaCommand(sourceProjectRoot).toArray(command); runInTerminalRequestArguments.setArgs(command); - outputLogger.sendConsoleOutput("Launching debugger in terminal"); + outputLogger.sendDebugServerOutput("Launching debugger in terminal"); context.getAdapter().runInTerminal(runInTerminalRequestArguments); } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java index ad29ed4e8b17..6ad923204f32 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java @@ -33,10 +33,13 @@ import com.sun.jdi.request.EventRequest; import com.sun.jdi.request.StepRequest; import org.ballerinalang.debugadapter.breakpoint.BalBreakpoint; +import org.ballerinalang.debugadapter.config.ClientConfigHolder; +import org.ballerinalang.debugadapter.jdi.JdiProxyException; 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.StackFrame; import org.eclipse.lsp4j.debug.StoppedEventArguments; import org.eclipse.lsp4j.debug.StoppedEventArgumentsReason; import org.slf4j.Logger; @@ -46,10 +49,12 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; import static org.ballerinalang.debugadapter.utils.PackageUtils.BAL_FILE_EXT; +import static org.ballerinalang.debugadapter.utils.PackageUtils.URI_SCHEME_BALA; import static org.ballerinalang.debugadapter.utils.ServerUtils.isBalStackFrame; /** @@ -141,8 +146,16 @@ private void processEvent(EventSet eventSet, Event event) { } else if (event instanceof BreakpointEvent bpEvent) { breakpointProcessor.processBreakpointEvent(bpEvent); } else if (event instanceof StepEvent stepEvent) { + ClientConfigHolder clientConfigs = context.getAdapter().getClientConfigHolder(); int threadId = (int) stepEvent.thread().uniqueID(); - if (isBallerinaSource(stepEvent.location())) { + // If the debug client is in low-code mode and stepping into external sources, need to step out again + // to reach the ballerina source. + // TODO: Revert once the low-code mode supports rendering external sources. + if (clientConfigs.isLowCodeMode() && hasSteppedIntoExternalSource(stepEvent)) { + context.getAdapter().getOutputLogger().sendDebugServerOutput("Debugging external sources is not " + + "supported in low-code mode. Stepping-out to reach the ballerina source."); + sendStepRequest(threadId, StepRequest.STEP_OUT); + } else if (isBallerinaSource(stepEvent.location())) { notifyStopEvent(event); } else { int stepType = ((StepRequest) event.request()).depth(); @@ -167,6 +180,43 @@ private void processEvent(EventSet eventSet, Event event) { } } + private boolean hasSteppedIntoExternalSource(StepEvent event) { + int stepType = ((StepRequest) event.request()).depth(); + if (stepType != StepRequest.STEP_INTO) { + return false; + } + try { + ThreadReferenceProxyImpl thread = context.getAdapter().getAllThreads().get((int) event.thread().uniqueID()); + Optional topFrame = thread.frames().stream() + .map(this::toDapStackFrame) + .filter(ServerUtils::isValidFrame).findFirst(); + // If the source path of the top frame contains the URI scheme of a bala file, it can be considered as an + // external source. + if (topFrame.isPresent()) { + String path = topFrame.get().getSource().getPath(); + return path.startsWith(URI_SCHEME_BALA); + } + return false; + } catch (JdiProxyException e) { + throw new RuntimeException(e); + } + } + + /** + * Coverts a JDI stack frame instance to a DAP stack frame instance. + */ + private StackFrame toDapStackFrame(StackFrameProxyImpl stackFrameProxy) { + try { + if (!isBalStackFrame(stackFrameProxy.getStackFrame())) { + return null; + } + BallerinaStackFrame balStackFrame = new BallerinaStackFrame(context, 0, stackFrameProxy); + return balStackFrame.getAsDAPStackFrame().orElse(null); + } catch (JdiProxyException e) { + return null; + } + } + void enableBreakpoints(String qClassName, LinkedHashMap breakpoints) { breakpointProcessor.addSourceBreakpoints(qClassName, breakpoints); if (context.getDebuggeeVM() == null) { diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java index 73b118cc7861..7b7e6adbc3d9 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java @@ -33,6 +33,7 @@ public class ClientConfigHolder { private final ClientConfigKind kind; private String sourcePath; private Integer debuggePort; + private Boolean isLowCodeMode; private ExtendedClientCapabilities extendedClientCapabilities; protected static final String ARG_FILE_PATH = "script"; @@ -43,6 +44,7 @@ public class ClientConfigHolder { private static final String ARG_SUPPORT_BP_VERIFICATION = "supportsBreakpointVerification"; private static final String ARG_SUPPORT_FAST_RUN = "supportsFastRun"; private static final String ARG_TERMINAL_KIND = "terminal"; + private static final String ARG_LOW_CODE_MODE = "lowCodeMode"; private static final String INTEGRATED_TERMINAL_KIND = "INTEGRATED"; private static final String EXTERNAL_TERMINAL_KIND = "EXTERNAL"; @@ -122,6 +124,21 @@ public RunInTerminalRequestArgumentsKind getRunInTerminalKind() { return null; } + public boolean isLowCodeMode() { + if (this.isLowCodeMode == null) { + Object isLowCodeMode = clientRequestArgs.get(ARG_LOW_CODE_MODE); + if (isLowCodeMode instanceof Boolean b) { + this.isLowCodeMode = b; + } else if (isLowCodeMode instanceof String s) { + this.isLowCodeMode = Boolean.parseBoolean(s); + } else { + this.isLowCodeMode = false; + } + } + + return isLowCodeMode; + } + protected void failIfConfigMissing(String configName) throws ClientConfigurationException { if (clientRequestArgs.get(configName) == null) { throw new ClientConfigurationException("Required client configuration missing: '" + configName + "'"); diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/PackageUtils.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/PackageUtils.java index 0ee0b4a8eba6..f9158377e908 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/PackageUtils.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/PackageUtils.java @@ -65,7 +65,7 @@ public final class PackageUtils { static final String PERSIST_DIR = "persist"; static final String TEST_PKG_POSTFIX = "$test"; private static final String URI_SCHEME_FILE = "file"; - private static final String URI_SCHEME_BALA = "bala"; + public static final String URI_SCHEME_BALA = "bala"; private static final String FILE_SEPARATOR_REGEX = File.separatorChar == '\\' ? "\\\\" : File.separator; From 100723acd17def85b27be9311bff125e0d63f8b5 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Fri, 3 Jan 2025 16:10:17 +0530 Subject: [PATCH 2/2] Improve error message --- .../org/ballerinalang/debugadapter/JDIEventProcessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java index 6ad923204f32..85dc12bf2566 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java @@ -152,8 +152,8 @@ private void processEvent(EventSet eventSet, Event event) { // to reach the ballerina source. // TODO: Revert once the low-code mode supports rendering external sources. if (clientConfigs.isLowCodeMode() && hasSteppedIntoExternalSource(stepEvent)) { - context.getAdapter().getOutputLogger().sendDebugServerOutput("Debugging external sources is not " + - "supported in low-code mode. Stepping-out to reach the ballerina source."); + DebugOutputLogger logger = context.getAdapter().getOutputLogger(); + logger.sendDebugServerOutput("Stepping into external sources is not supported in low-code mode."); sendStepRequest(threadId, StepRequest.STEP_OUT); } else if (isBallerinaSource(stepEvent.location())) { notifyStopEvent(event);