diff --git a/src/org/rascalmpl/dap/RascalDebugAdapter.java b/src/org/rascalmpl/dap/RascalDebugAdapter.java index 24c4ce04b7..df3c865e54 100644 --- a/src/org/rascalmpl/dap/RascalDebugAdapter.java +++ b/src/org/rascalmpl/dap/RascalDebugAdapter.java @@ -31,6 +31,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -154,7 +155,6 @@ public CompletableFuture initialize(InitializeRequestArguments arg Capabilities capabilities = new Capabilities(); capabilities.setSupportsConfigurationDoneRequest(true); - capabilities.setExceptionBreakpointFilters(new ExceptionBreakpointsFilter[]{}); capabilities.setSupportsStepBack(false); capabilities.setSupportsRestartFrame(false); capabilities.setSupportsSetVariable(false); @@ -162,6 +162,14 @@ public CompletableFuture initialize(InitializeRequestArguments arg capabilities.setSupportsCompletionsRequest(true); capabilities.setSupportsConditionalBreakpoints(true); + ExceptionBreakpointsFilter[] exceptionFilters = new ExceptionBreakpointsFilter[1]; + ExceptionBreakpointsFilter exFilter = new ExceptionBreakpointsFilter(); + exFilter.setFilter("rascalExceptions"); + exFilter.setLabel("Rascal Exceptions"); + exFilter.setDescription("Break when a Rascal exception is thrown"); + exceptionFilters[0] = exFilter; + capabilities.setExceptionBreakpointFilters(exceptionFilters); + return capabilities; }, ownExecutor); } @@ -298,6 +306,16 @@ private static ITree locateBreakableTree(ITree tree, int line) { return null; } + @Override + public CompletableFuture setExceptionBreakpoints(SetExceptionBreakpointsArguments args) { + return CompletableFuture.supplyAsync(() -> { + SetExceptionBreakpointsResponse response = new SetExceptionBreakpointsResponse(); + debugHandler.setSuspendOnException(Arrays.stream(args.getFilters()).anyMatch("rascalExceptions"::equals)); + response.setBreakpoints(new Breakpoint[0]); + return response; + }, ownExecutor); + } + @Override public CompletableFuture attach(Map args) { client.initialized(); diff --git a/src/org/rascalmpl/dap/RascalDebugEventTrigger.java b/src/org/rascalmpl/dap/RascalDebugEventTrigger.java index a5a208353c..1f925519df 100644 --- a/src/org/rascalmpl/dap/RascalDebugEventTrigger.java +++ b/src/org/rascalmpl/dap/RascalDebugEventTrigger.java @@ -27,6 +27,7 @@ package org.rascalmpl.dap; import io.usethesource.vallang.ISourceLocation; + import org.eclipse.lsp4j.debug.StoppedEventArguments; import org.eclipse.lsp4j.debug.services.IDebugProtocolClient; import org.rascalmpl.dap.breakpoint.BreakpointsCollection; @@ -132,6 +133,18 @@ public void fireSuspendByClientRequestEvent() { client.stopped(stoppedEventArguments); } + @Override + public void fireSuspendByExceptionEvent(Exception exception) { + suspendedState.suspended(); + + StoppedEventArguments stoppedEventArguments = new StoppedEventArguments(); + stoppedEventArguments.setThreadId(RascalDebugAdapter.mainThreadID); + stoppedEventArguments.setDescription("Paused on exception."); + stoppedEventArguments.setReason("exception"); + stoppedEventArguments.setText(exception.getMessage()); + client.stopped(stoppedEventArguments); + } + @Override public void fireSuspendEvent(RascalEvent.Detail detail) {} } diff --git a/src/org/rascalmpl/debug/AbstractInterpreterEventTrigger.java b/src/org/rascalmpl/debug/AbstractInterpreterEventTrigger.java index 40ec05ea4d..16bec99a93 100644 --- a/src/org/rascalmpl/debug/AbstractInterpreterEventTrigger.java +++ b/src/org/rascalmpl/debug/AbstractInterpreterEventTrigger.java @@ -143,6 +143,15 @@ public void fireSuspendByBreakpointEvent(Object data) { fireEvent(event); } + public void fireSuspendByExceptionEvent(Exception exception) { + RascalEvent event = new RascalEvent(source, + RascalEvent.Kind.SUSPEND, + RascalEvent.Detail.EXCEPTION); + event.setData(exception); + + fireEvent(event); + } + /** * Fires a idle event for this interpreter. E.g. this happens when the REPL * is waiting for another command input. diff --git a/src/org/rascalmpl/debug/DebugHandler.java b/src/org/rascalmpl/debug/DebugHandler.java index 4b73a77994..0c835b6150 100644 --- a/src/org/rascalmpl/debug/DebugHandler.java +++ b/src/org/rascalmpl/debug/DebugHandler.java @@ -60,6 +60,18 @@ public final class DebugHandler implements IDebugHandler, IRascalRuntimeEvaluati */ private boolean suspendRequested; + private boolean suspendOnException = false; + + private Exception lastExceptionHandled = null; + + public boolean getSuspendOnException() { + return suspendOnException; + } + + public void setSuspendOnException(boolean suspendOnException) { + this.suspendOnException = suspendOnException; + } + /** * Indicates that the evaluator is suspended. Also used for suspending / blocking the evaluator. */ @@ -160,7 +172,21 @@ public void suspended(Object runtime, IntSupplier getCallStackSize, AbstractAST updateSuspensionState(getCallStackSize.getAsInt(), currentAST); getEventTrigger().fireSuspendByClientRequestEvent(); setSuspendRequested(false); - } + } else if(getSuspendOnException() && runtime instanceof Evaluator && ((Evaluator) runtime).getCurrentException() != null ) { + // Suspension due to exception + Evaluator eval = (Evaluator) runtime; + Exception e = eval.getCurrentException(); + if(lastExceptionHandled != null && e == lastExceptionHandled){ + return; // already handled this exception + } + if(handleExceptionSuspension(eval, e)){ + lastExceptionHandled = e; + updateSuspensionState(getCallStackSize.getAsInt(), currentAST); + getEventTrigger().fireSuspendByExceptionEvent(e); + } else { + return; + } + } else { AbstractAST location = currentAST; @@ -239,7 +265,7 @@ public void suspended(Object runtime, IntSupplier getCallStackSize, AbstractAST break; } } - + /* * Waiting until GUI triggers end of suspension. */ @@ -252,6 +278,21 @@ public void suspended(Object runtime, IntSupplier getCallStackSize, AbstractAST } } + private boolean handleExceptionSuspension(Evaluator eval, Exception e) { + if(e instanceof Throw){ + Throw thr = (Throw) e; + IValue excValue = thr.getException(); + if(excValue.getType().isAbstractData()){ + // We ignore suspension that happens due in standard library code for RuntimeExceptions + if(excValue.getType().getName().equals("RuntimeException")){ + return !eval.getCurrentAST().getLocation().getScheme().equals("std"); + } + } + return true; + } + return false; + } + protected AbstractAST getReferenceAST() { return referenceAST; } diff --git a/src/org/rascalmpl/debug/RascalEvent.java b/src/org/rascalmpl/debug/RascalEvent.java index 705d7be6cb..27d4288d2c 100644 --- a/src/org/rascalmpl/debug/RascalEvent.java +++ b/src/org/rascalmpl/debug/RascalEvent.java @@ -31,7 +31,7 @@ public enum Kind { * Details additional to {@link Kind}. */ public enum Detail { - UNSPECIFIED, CLIENT_REQUEST, STEP_INTO, STEP_OVER, STEP_END, BREAKPOINT, STEP_OUT + UNSPECIFIED, CLIENT_REQUEST, STEP_INTO, STEP_OVER, STEP_END, BREAKPOINT, STEP_OUT, EXCEPTION } private final Kind kind; diff --git a/src/org/rascalmpl/interpreter/Evaluator.java b/src/org/rascalmpl/interpreter/Evaluator.java index c90d4a159b..73b100c76a 100755 --- a/src/org/rascalmpl/interpreter/Evaluator.java +++ b/src/org/rascalmpl/interpreter/Evaluator.java @@ -167,6 +167,11 @@ public void decCallNesting() { */ private AbstractAST currentAST; + /** + * Used in debugger exception handling + */ + private Exception currentException; + /** * True if we're doing profiling */ @@ -780,6 +785,10 @@ public AbstractAST getCurrentAST() { return currentAST; } + public Exception getCurrentException() { + return currentException; + } + public void addRascalSearchPathContributor(IRascalSearchPathContributor contrib) { rascalPathResolver.addPathContributor(contrib); } @@ -1589,6 +1598,17 @@ public void notifyAboutSuspension(AbstractAST currentAST) { } } + @Override + public void notifyAboutSuspensionException(Exception t) { + currentException = t; + if (!suspendTriggerListeners.isEmpty()) { // remove the breakable condition since exception can happen anywhere + for (IRascalSuspendTriggerListener listener : suspendTriggerListeners) { + listener.suspended(this, () -> getCallStack().size(), currentAST); + } + } + currentException = null; + } + public AbstractInterpreterEventTrigger getEventTrigger() { return eventTrigger; } diff --git a/src/org/rascalmpl/interpreter/IEvaluator.java b/src/org/rascalmpl/interpreter/IEvaluator.java index 1a759244ea..529ff5b66e 100644 --- a/src/org/rascalmpl/interpreter/IEvaluator.java +++ b/src/org/rascalmpl/interpreter/IEvaluator.java @@ -60,6 +60,7 @@ public interface IEvaluator extends IEvaluatorContext { * @param currentAST the AST that is causes the suspension. */ public void notifyAboutSuspension(AbstractAST currentAST); + public void notifyAboutSuspensionException(Exception t); /* * Evaluation. diff --git a/src/org/rascalmpl/interpreter/result/RascalFunction.java b/src/org/rascalmpl/interpreter/result/RascalFunction.java index 2c17fb52ae..a9e9ca0071 100644 --- a/src/org/rascalmpl/interpreter/result/RascalFunction.java +++ b/src/org/rascalmpl/interpreter/result/RascalFunction.java @@ -369,6 +369,9 @@ public Result call(Type[] actualStaticTypes, IValue[] actuals, Map