Skip to content

Commit

Permalink
Merge pull request #43732 from NipunaRanasinghe/debugger-lowcode-mode
Browse files Browse the repository at this point in the history
[Debugger] Disable stepping into external sources in low-code mode
  • Loading branch information
NipunaRanasinghe authored Jan 8, 2025
2 parents 2c1c6f6 + 100723a commit e7d7a09
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ public CompletableFuture<Void> 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);
}
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand Down Expand Up @@ -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)) {
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);
} else {
int stepType = ((StepRequest) event.request()).depth();
Expand All @@ -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<StackFrame> 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<Integer, BalBreakpoint> breakpoints) {
breakpointProcessor.addSourceBreakpoints(qClassName, breakpoints);
if (context.getDebuggeeVM() == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";

Expand Down Expand Up @@ -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 + "'");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down

0 comments on commit e7d7a09

Please sign in to comment.