Catch Polyglot Exceptions (#1200)

`Panic.recover` catches host exceptions wrapping them in
`Polyglot_Error` atom constructor.
This commit is contained in:
Dmitry Bushev 2020-10-07 23:43:12 +03:00 committed by GitHub
parent a2d3b9fe01
commit 1fbf3ad692
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 7 deletions

View File

@ -1,9 +1,14 @@
package org.enso.interpreter.node.expression.builtin.error;
import com.oracle.truffle.api.TruffleException;
import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.error.RuntimeError;
@ -13,14 +18,32 @@ import org.enso.interpreter.runtime.state.Stateful;
type = "Panic",
name = "catch",
description = "Executes an action and converts any Panic thrown by it into an Error")
public class CatchPanicNode extends Node {
public abstract class CatchPanicNode extends Node {
private @Child ThunkExecutorNode thunkExecutorNode = ThunkExecutorNode.build();
Stateful execute(@MonadicState Object state, Object _this, Thunk action) {
static CatchPanicNode build() {
return CatchPanicNodeGen.create();
}
abstract Stateful execute(@MonadicState Object state, Object _this, Thunk action);
@Specialization
Stateful doExecute(
@MonadicState Object state,
Object _this,
Thunk action,
@CachedContext(Language.class) Context ctx) {
try {
return thunkExecutorNode.executeThunk(action, state, false);
} catch (PanicException e) {
return new Stateful(state, new RuntimeError(e.getExceptionObject()));
} catch (Throwable e) {
if (ctx.getEnvironment().isHostException(e)) {
Object cause = ((TruffleException) e).getExceptionObject();
return new Stateful(
state, new RuntimeError(ctx.getBuiltins().error().makePolyglotError(cause)));
}
throw e;
}
}
}

View File

@ -14,6 +14,7 @@ public class Error {
private final AtomConstructor inexhaustivePatternMatchError;
private final AtomConstructor uninitializedState;
private final AtomConstructor noSuchMethodError;
private final AtomConstructor polyglotError;
/**
* Creates and registers the relevant constructors.
@ -43,12 +44,17 @@ public class Error {
.initializeFields(
new ArgumentDefinition(0, "target", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "method_name", ArgumentDefinition.ExecutionMode.EXECUTE));
polyglotError =
new AtomConstructor("Polyglot_Error", scope)
.initializeFields(
new ArgumentDefinition(0, "cause", ArgumentDefinition.ExecutionMode.EXECUTE));
scope.registerConstructor(syntaxError);
scope.registerConstructor(compileError);
scope.registerConstructor(inexhaustivePatternMatchError);
scope.registerConstructor(uninitializedState);
scope.registerConstructor(noSuchMethodError);
scope.registerConstructor(polyglotError);
}
/** @return the builtin {@code Syntax_Error} atom constructor. */
@ -81,4 +87,14 @@ public class Error {
public Atom makeNoSuchMethodError(Object target, String name) {
return noSuchMethodError.newInstance(target, Text.create(name));
}
/**
* Creates an instance of the runtime representation of a {@code Polyglot_Error}.
*
* @param cause the cause of the error.
* @return a runtime representation of the polyglot error.
*/
public Atom makePolyglotError(Object cause) {
return polyglotError.newInstance(cause);
}
}

View File

@ -2,6 +2,7 @@ package org.enso.interpreter.runtime.callable.atom;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.*;
@ -120,6 +121,7 @@ public class Atom implements TruffleObject {
@ExportMessage
static class InvokeMember {
static UnresolvedSymbol buildSym(AtomConstructor cons, String name) {
return UnresolvedSymbol.build(name, cons.getDefinitionScope());
}
@ -130,11 +132,9 @@ public class Atom implements TruffleObject {
Atom receiver,
String member,
Object[] arguments,
@Cached(value = "receiver.getConstructor()", allowUncached = true)
AtomConstructor cachedConstructor,
@Cached(value = "member", allowUncached = true) String cachedMember,
@Cached(value = "buildSym(cachedConstructor, cachedMember)", allowUncached = true)
UnresolvedSymbol cachedSym,
@Cached(value = "receiver.getConstructor()") AtomConstructor cachedConstructor,
@Cached(value = "member") String cachedMember,
@Cached(value = "buildSym(cachedConstructor, cachedMember)") UnresolvedSymbol cachedSym,
@CachedLibrary("cachedSym") InteropLibrary symbols)
throws UnsupportedMessageException, ArityException, UnsupportedTypeException {
Object[] args = new Object[arguments.length + 1];
@ -142,6 +142,18 @@ public class Atom implements TruffleObject {
System.arraycopy(arguments, 0, args, 1, arguments.length);
return symbols.execute(cachedSym, args);
}
@Specialization(replaces = "doCached")
static Object doUncached(
Atom receiver,
String member,
Object[] arguments,
@CachedLibrary(limit = "1") InteropLibrary symbols)
throws UnsupportedMessageException, ArityException, UnsupportedTypeException {
UnresolvedSymbol symbol = buildSym(receiver.getConstructor(), member);
return doCached(
receiver, member, arguments, receiver.getConstructor(), member, symbol, symbols);
}
}
@ExportMessage

View File

@ -133,5 +133,28 @@ class ErrorsTest extends InterpreterTest {
val code = "main = 10.catch (x -> x + 1)"
eval(code) shouldEqual 10
}
"catch polyglot errors" in {
val code =
"""from Builtins import all
|polyglot java import java.lang.Long
|
|main =
| caught = Panic.recover (Long.parseLong (Array.new_1 "oops"))
| IO.println caught
| cause = caught.catch <| case _ of
| Polyglot_Error err -> err
| _ -> "fail"
| IO.println cause
| message = cause.getMessage (Array.new 0)
| IO.println message
|""".stripMargin
eval(code)
consumeOut shouldEqual List(
"""(Error: (Polyglot_Error java.lang.NumberFormatException: For input string: "oops"))""",
"""java.lang.NumberFormatException: For input string: "oops"""",
"""For input string: "oops""""
)
}
}
}