Skip to content

Commit

Permalink
[GR-7702] Debugger suspends on exceptions.
Browse files Browse the repository at this point in the history
  • Loading branch information
entlicher committed Apr 3, 2018
1 parent 23d10df commit d1343de
Show file tree
Hide file tree
Showing 32 changed files with 2,464 additions and 287 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,23 @@ public static InspectorTester start(boolean suspend) throws InterruptedException
}

public void finish() throws InterruptedException {
finish(false);
}

public Throwable finishErr() throws InterruptedException {
return finish(true);
}

private Throwable finish(boolean expectError) throws InterruptedException {
synchronized (exec) {
exec.done = true;
exec.catchError = expectError;
exec.notifyAll();
}
exec.join();
RemoteObject.resetIDs();
TruffleExecutionContext.resetIDs();
return exec.error;
}

public long getContextId() {
Expand Down Expand Up @@ -135,6 +145,8 @@ private static class InspectExecThread extends Thread implements InspectServerSe
private boolean done = false;
private final StringBuilder receivedMessages = new StringBuilder();
private final Semaphore initialized = new Semaphore(0);
private boolean catchError;
private Throwable error;

InspectExecThread(boolean suspend) {
super("Inspector Executor");
Expand Down Expand Up @@ -174,6 +186,14 @@ public void run() {
valueFuture.complete(value);
}
} while (!done);
} catch (ThreadDeath td) {
throw td;
} catch (Throwable t) {
if (catchError) {
error = t;
} else {
throw t;
}
} finally {
inspect.dispose();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ public class SLInspectDebugTest {
" x = x + y; y = x / y; return x * y;\n" +
" \n" +
"}";
private static final String CODE_THROW = "function main() {\n" +
" i = \"0\";\n" +
" return invert(i);\n" +
"}\n" +
"function invert(n) {\n" +
" x = 10 / n;\n" +
" return x;\n" +
"}\n";
private static final String SL_BUILTIN_URI = "truffle:8350e5a3e24c153df2275c9f80692773/SL builtin";

private InspectorTester tester;
Expand Down Expand Up @@ -1026,6 +1034,46 @@ public void testPossibleBreakpoints() throws Exception {
tester.finish();
}

@Test
public void testThrown() throws Exception {
tester = InspectorTester.start(false);
Source source = Source.newBuilder("sl", CODE_THROW, "SLThrow.sl").build();
tester.sendMessage("{\"id\":1,\"method\":\"Runtime.enable\"}");
tester.sendMessage("{\"id\":2,\"method\":\"Debugger.enable\"}");
String srcURL = ScriptsHandler.getNiceStringFromURI(source.getURI());
assertTrue(tester.compareReceivedMessages(
"{\"result\":{},\"id\":1}\n" +
"{\"result\":{},\"id\":2}\n"));
tester.sendMessage("{\"id\":3,\"method\":\"Debugger.setPauseOnExceptions\",\"params\":{\"state\":\"uncaught\"}}");
assertTrue(tester.compareReceivedMessages(
"{\"result\":{},\"id\":3}\n"));

tester.eval(source);
long id = tester.getContextId();
assertTrue(tester.compareReceivedMessages(
"{\"method\":\"Debugger.scriptParsed\",\"params\":{\"endLine\":0,\"scriptId\":\"0\",\"endColumn\":0,\"startColumn\":0,\"startLine\":0,\"length\":0,\"executionContextId\":" + id + ",\"url\":\"" + SL_BUILTIN_URI + "\",\"hash\":\"ffffffffffffffffffffffffffffffffffffffff\"}}\n" +
"{\"method\":\"Debugger.scriptParsed\",\"params\":{\"endLine\":7,\"scriptId\":\"1\",\"endColumn\":1,\"startColumn\":0,\"startLine\":0,\"length\":100,\"executionContextId\":" + id + ",\"url\":\"" + srcURL + "\",\"hash\":\"da38785af1cf0829f047f02ffe94b4d3ff485978\"}}\n"));
assertTrue(tester.compareReceivedMessages(
"{\"method\":\"Debugger.paused\",\"params\":{\"reason\":\"exception\",\"data\":{\"uncaught\":true},\"hitBreakpoints\":[]," +
"\"callFrames\":[{\"callFrameId\":\"0\",\"functionName\":\"invert\"," +
"\"scopeChain\":[{\"name\":\"invert\",\"type\":\"local\",\"object\":{\"description\":\"invert\",\"type\":\"object\",\"objectId\":\"1\"}}," +
"{\"name\":\"global\",\"type\":\"global\",\"object\":{\"description\":\"global\",\"type\":\"object\",\"objectId\":\"2\"}}]," +
"\"functionLocation\":{\"scriptId\":\"1\",\"columnNumber\":9,\"lineNumber\":4}," +
"\"location\":{\"scriptId\":\"1\",\"columnNumber\":2,\"lineNumber\":5}}," +
"{\"callFrameId\":\"1\",\"functionName\":\"main\"," +
"\"scopeChain\":[{\"name\":\"main\",\"type\":\"local\",\"object\":{\"description\":\"main\",\"type\":\"object\",\"objectId\":\"3\"}}," +
"{\"name\":\"global\",\"type\":\"global\",\"object\":{\"description\":\"global\",\"type\":\"object\",\"objectId\":\"4\"}}]," +
"\"functionLocation\":{\"scriptId\":\"1\",\"columnNumber\":9,\"lineNumber\":0}," +
"\"location\":{\"scriptId\":\"1\",\"columnNumber\":9,\"lineNumber\":2}}" +
"]}}\n"));
// Resume to finish:
tester.sendMessage("{\"id\":10,\"method\":\"Debugger.resume\"}");
assertTrue(tester.compareReceivedMessages(
"{\"result\":{},\"id\":10}\n" +
"{\"method\":\"Debugger.resumed\"}\n"));
tester.finishErr();
}

// @formatter:on
// CheckStyle: resume line length check
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import com.oracle.truffle.tools.chromeinspector.server.CommandProcessException;
import com.oracle.truffle.tools.chromeinspector.types.Location;
import com.oracle.truffle.tools.chromeinspector.types.Script;
import java.util.concurrent.atomic.AtomicReference;

final class BreakpointsHandler {

Expand All @@ -56,6 +57,7 @@ final class BreakpointsHandler {
private final Map<Breakpoint, Long> bpIDs = new HashMap<>();
private final Map<Breakpoint, SourceSection> resolvedBreakpoints = new HashMap<>();
private final Map<Long, LoadScriptListener> scriptListeners = new HashMap<>();
private final AtomicReference<Breakpoint> exceptionBreakpoint = new AtomicReference<>();

BreakpointsHandler(DebuggerSession ds, ScriptsHandler slh, Supplier<EventHandler> eventHandler) {
this.ds = ds;
Expand Down Expand Up @@ -171,6 +173,18 @@ void createOneShotBreakpoint(Location location) throws CommandProcessException {
ds.install(bp);
}

void setExceptionBreakpoint(boolean caught, boolean uncaught) {
Breakpoint newBp = null;
if (caught || uncaught) {
newBp = Breakpoint.newExceptionBuilder(caught, uncaught).build();
ds.install(newBp);
}
Breakpoint oldBp = exceptionBreakpoint.getAndSet(newBp);
if (oldBp != null) {
oldBp.dispose();
}
}

private static Breakpoint.Builder createBuilder(Source source, int line, int column) {
Breakpoint.Builder builder = Breakpoint.newBuilder(source).lineIs(line);
if (column > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ void removeLoadScriptListener(LoadScriptListener listener) {
}
}

int assureLoaded(Source source) {
public int assureLoaded(Source source) {
Script scr;
URI uri = source.getURI();
int id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

import com.oracle.truffle.api.TruffleException;
import com.oracle.truffle.api.debug.Breakpoint;
import com.oracle.truffle.api.debug.DebugException;
import com.oracle.truffle.api.debug.DebugScope;
import com.oracle.truffle.api.debug.DebugStackFrame;
import com.oracle.truffle.api.debug.DebugValue;
Expand Down Expand Up @@ -76,6 +77,8 @@
import com.oracle.truffle.tools.chromeinspector.types.RemoteObject;
import com.oracle.truffle.tools.chromeinspector.types.Scope;
import com.oracle.truffle.tools.chromeinspector.types.Script;
import java.util.HashSet;
import java.util.Set;

public final class TruffleDebugger extends DebuggerDomain {

Expand Down Expand Up @@ -170,7 +173,20 @@ public void setBlackboxPatterns(String[] patterns) {
}

@Override
public void setPauseOnExceptions(String state) {
public void setPauseOnExceptions(String state) throws CommandProcessException {
switch (state) {
case "none":
bph.setExceptionBreakpoint(false, false);
break;
case "uncaught":
bph.setExceptionBreakpoint(false, true);
break;
case "all":
bph.setExceptionBreakpoint(true, true);
break;
default:
throw new CommandProcessException("Unknown Pause on exceptions mode: " + state);
}

}

Expand Down Expand Up @@ -384,7 +400,18 @@ public void setBreakpointsActive(Optional<Boolean> active) throws CommandProcess
if (!active.isPresent()) {
throw new CommandProcessException("Must specify active argument.");
}
ds.setBreakpointsActive(active.get());
ds.setBreakpointsActive(Breakpoint.Kind.SOURCE_LOCATION, active.get());
}

@Override
public void setSkipAllPauses(Optional<Boolean> skip) throws CommandProcessException {
if (!skip.isPresent()) {
throw new CommandProcessException("Must specify 'skip' argument.");
}
boolean active = !skip.get();
for (Breakpoint.Kind kind : Breakpoint.Kind.values()) {
ds.setBreakpointsActive(kind, active);
}
}

@Override
Expand Down Expand Up @@ -476,7 +503,7 @@ public JSONObject executeCommand() throws CommandProcessException {
jsonResult.put("result", err);
} catch (GuestLanguageException e) {
jsonResult = new JSONObject();
TruffleRuntime.fillExceptionDetails(jsonResult, e);
TruffleRuntime.fillExceptionDetails(jsonResult, e, context);
}
return new Params(jsonResult);
}
Expand Down Expand Up @@ -651,14 +678,20 @@ public void onSuspend(SuspendedEvent se) {
commandLazyResponse = null;
} else {
jsonParams.put("callFrames", getFramesParam(callFrames));
jsonParams.put("reason", "other");
List<Breakpoint> breakpoints = se.getBreakpoints();
JSONArray bpArr = new JSONArray();
Set<Breakpoint.Kind> kinds = new HashSet<>(1);
for (Breakpoint bp : breakpoints) {
String id = bph.getId(bp);
if (id != null) {
bpArr.put(id);
}
kinds.add(bp.getKind());
}
jsonParams.put("reason", getHaltReason(kinds));
JSONObject data = getHaltData(se);
if (data != null) {
jsonParams.put("data", data);
}
jsonParams.put("hitBreakpoints", bpArr);

Expand Down Expand Up @@ -742,6 +775,38 @@ private synchronized void unlock() {
notify();
}

private String getHaltReason(Set<Breakpoint.Kind> kinds) {
if (kinds.size() > 1) {
return "ambiguous";
} else {
if (kinds.contains(Breakpoint.Kind.HALT_INSTRUCTION)) {
return "debugCommand";
} else if (kinds.contains(Breakpoint.Kind.EXCEPTION)) {
return "exception";
} else {
return "other";
}
}
}

private JSONObject getHaltData(SuspendedEvent se) {
DebugException exception = se.getException();
if (exception == null) {
return null;
}
boolean uncaught = exception.getCatchLocation() == null;
DebugValue exceptionObject = exception.getExceptionObject();
JSONObject data;
if (exceptionObject != null) {
RemoteObject remoteObject = context.createAndRegister(exceptionObject);
data = remoteObject.toJSON();
} else {
data = new JSONObject();
}
data.put("uncaught", uncaught);
return data;
}

private class SchedulerThreadFactory implements ThreadFactory {

private final ThreadGroup group;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@
import java.util.concurrent.atomic.AtomicLong;

import com.oracle.truffle.api.InstrumentInfo;
import com.oracle.truffle.api.TruffleException;
import com.oracle.truffle.api.debug.DebugException;
import com.oracle.truffle.api.debug.DebugValue;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;

import com.oracle.truffle.tools.chromeinspector.instrument.Enabler;
import com.oracle.truffle.tools.chromeinspector.instrument.SourceLoadInstrument;
import com.oracle.truffle.tools.chromeinspector.server.CommandProcessException;
import com.oracle.truffle.tools.chromeinspector.types.RemoteObject;

/**
* The Truffle engine execution context.
Expand Down Expand Up @@ -137,6 +139,14 @@ synchronized RemoteObjectsHandler getRemoteObjectsHandler() {
return roh;
}

public RemoteObject createAndRegister(DebugValue value) {
RemoteObject ro = new RemoteObject(value, getErr());
if (ro.getId() != null) {
getRemoteObjectsHandler().register(ro);
}
return ro;
}

void setSuspendThreadExecutor(SuspendedThreadExecutor suspendThreadExecutor) {
this.suspendThreadExecutor = suspendThreadExecutor;
}
Expand Down Expand Up @@ -168,8 +178,8 @@ public void cancel() {
params = cf.get();
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
if (cause instanceof TruffleException && !((TruffleException) cause).isInternalError()) {
throw new GuestLanguageException((TruffleException) cause);
if (cause instanceof DebugException && !((DebugException) cause).isInternalError()) {
throw new GuestLanguageException((DebugException) cause);
}
if (cause instanceof CommandProcessException) {
throw (CommandProcessException) cause;
Expand Down Expand Up @@ -249,16 +259,17 @@ static void raiseResuming() throws NoSuspendedThreadException {
}
}

/** A checked variant of DebugException. */
static final class GuestLanguageException extends Exception {

private static final long serialVersionUID = 7386388514508637851L;

private GuestLanguageException(TruffleException truffleException) {
super((Throwable) truffleException);
private GuestLanguageException(DebugException truffleException) {
super(truffleException);
}

TruffleException getTruffleException() {
return (TruffleException) getCause();
DebugException getDebugException() {
return (DebugException) getCause();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import com.oracle.truffle.tools.chromeinspector.instrument.Enabler;
import com.oracle.truffle.tools.chromeinspector.instrument.OutputConsumerInstrument;
import com.oracle.truffle.tools.chromeinspector.server.CommandProcessException;
import com.oracle.truffle.tools.chromeinspector.types.ExceptionDetails;
import com.oracle.truffle.tools.chromeinspector.types.InternalPropertyDescriptor;
import com.oracle.truffle.tools.chromeinspector.types.PropertyDescriptor;
import com.oracle.truffle.tools.chromeinspector.types.RemoteObject;
Expand Down Expand Up @@ -336,11 +337,13 @@ private JSONObject createCodecompletion(DebugValue value) {
return result;
}

static void fillExceptionDetails(JSONObject obj, GuestLanguageException ex) {
JSONObject exceptionDetails = new JSONObject();
exceptionDetails.put("text", ex.getLocalizedMessage());
// TODO: add more details
obj.put("exceptionDetails", exceptionDetails);
private void fillExceptionDetails(JSONObject obj, GuestLanguageException ex) {
fillExceptionDetails(obj, ex, context);
}

static void fillExceptionDetails(JSONObject obj, GuestLanguageException ex, TruffleExecutionContext context) {
ExceptionDetails exceptionDetails = new ExceptionDetails(ex.getDebugException());
obj.put("exceptionDetails", exceptionDetails.createJSON(context));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ public String getScriptId() {
}
}

public Optional<Boolean> getBreakpointsActive() {
if (json.has("active")) {
return Optional.of(json.getBoolean("active"));
public Optional<Boolean> getBoolean(String name) {
if (json.has(name)) {
return Optional.of(json.getBoolean(name));
} else {
return Optional.empty();
}
Expand Down
Loading

0 comments on commit d1343de

Please sign in to comment.