Skip to content

Commit

Permalink
Allow catching exceptions in other languages
Browse files Browse the repository at this point in the history
- Add support for InteropLibrary's isException/throwException.
- Change default for headless option to true (headless should only be false if TruffleSqueak is started through its launcher).
- Throw SmalltalkExceptions as SqueakInteropException in case another language has triggered the code execution. This makes it possible to catch exceptions in other languages (e.g. JS: `try { Polyglot.eval("smalltalk", "1/0") } catch (error) { console.error("Got:", error) }`)
- Update in-image code to include an appropriate test

Closes #117
  • Loading branch information
fniephaus committed Jun 17, 2020
1 parent f3b3dcc commit ddef572
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2611,6 +2611,7 @@ IntegerTest>>testSqrtFloor=passing
IntegerTest>>testSqrtRem=passing
IntegerTest>>testTwoComplementBitLogicWithCarry=passing
IntegerTest>>testTwoComplementRightShift=passing
InteropTest>>testExceptionHandling=passing
InteropTest>>testMetadataAPIs=passing
InteropTest>>testSmalltalkDNU=passing

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public final class SqueakOptions {
public static final OptionKey<String> ImageArguments = new OptionKey<>("");

@Option(name = SqueakLanguageOptions.HEADLESS, category = OptionCategory.USER, stability = OptionStability.STABLE, help = SqueakLanguageOptions.HEADLESS_HELP)//
public static final OptionKey<Boolean> Headless = new OptionKey<>(false);
public static final OptionKey<Boolean> Headless = new OptionKey<>(true);

@Option(name = SqueakLanguageOptions.INTERCEPT_MESSAGES, category = OptionCategory.INTERNAL, stability = OptionStability.EXPERIMENTAL, help = SqueakLanguageOptions.INTERCEPT_MESSAGES_HELP)//
public static final OptionKey<String> InterceptMessages = new OptionKey<>("");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
package de.hpi.swa.trufflesqueak.exceptions;

import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleException;
import com.oracle.truffle.api.nodes.ControlFlowException;
Expand All @@ -14,6 +15,7 @@
import de.hpi.swa.trufflesqueak.SqueakLanguage;
import de.hpi.swa.trufflesqueak.model.NativeObject;
import de.hpi.swa.trufflesqueak.model.PointersObject;
import de.hpi.swa.trufflesqueak.model.layout.ObjectLayouts.EXCEPTION;
import de.hpi.swa.trufflesqueak.model.layout.ObjectLayouts.SYNTAX_ERROR_NOTIFICATION;
import de.hpi.swa.trufflesqueak.nodes.AbstractNode;
import de.hpi.swa.trufflesqueak.util.ArrayUtils;
Expand Down Expand Up @@ -159,6 +161,42 @@ public boolean isExit() {
}
}

public static final class SqueakInteropException extends RuntimeException implements TruffleException {
private static final long serialVersionUID = 1L;
private final PointersObject squeakException;

public SqueakInteropException(final PointersObject original) {
squeakException = original;
}

@Override
public String getMessage() {
CompilerAsserts.neverPartOfCompilation();
final Object messageText = squeakException.instVarAt0Slow(EXCEPTION.MESSAGE_TEXT);
if (messageText instanceof NativeObject && ((NativeObject) messageText).isString()) {
return ((NativeObject) messageText).asStringUnsafe();
} else {
return squeakException.toString();
}
}

@SuppressWarnings("sync-override")
@Override
public Throwable fillInStackTrace() {
return this;
}

@Override
public Object getExceptionObject() {
return squeakException;
}

@Override
public Node getLocation() {
return null;
}
}

public static final class SqueakInterrupt extends RuntimeException implements TruffleException {
private static final long serialVersionUID = 1L;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ public final class SqueakImageContext {
public ContextObject lastSeenContext;

@CompilationFinal private ClassObject compilerClass;
@CompilationFinal private ClassObject exceptionClass;
@CompilationFinal private ClassObject parserClass;
private PointersObject parserSharedInstance;
@CompilationFinal private PointersObject scheduler;
Expand Down Expand Up @@ -381,6 +382,14 @@ public void setCompilerClass(final ClassObject classObject) {
compilerClass = classObject;
}

public ClassObject getExceptionClass() {
if (exceptionClass == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
exceptionClass = (ClassObject) evaluate("Exception");
}
return exceptionClass;
}

public ClassObject getParserClass() {
return parserClass;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@
package de.hpi.swa.trufflesqueak.model;

import com.oracle.truffle.api.CompilerAsserts;

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;

import de.hpi.swa.trufflesqueak.SqueakLanguage;
import de.hpi.swa.trufflesqueak.exceptions.SqueakExceptions.SqueakInteropException;
import de.hpi.swa.trufflesqueak.image.SqueakImageChunk;
import de.hpi.swa.trufflesqueak.image.SqueakImageContext;
import de.hpi.swa.trufflesqueak.image.SqueakImageWriter;
Expand All @@ -19,11 +28,13 @@
import de.hpi.swa.trufflesqueak.model.layout.ObjectLayouts.POINT;
import de.hpi.swa.trufflesqueak.model.layout.ObjectLayouts.PROCESS;
import de.hpi.swa.trufflesqueak.model.layout.ObjectLayouts.SPECIAL_OBJECT;
import de.hpi.swa.trufflesqueak.nodes.InheritsFromNode;
import de.hpi.swa.trufflesqueak.nodes.accessing.AbstractPointersObjectNodes.AbstractPointersObjectReadNode;
import de.hpi.swa.trufflesqueak.nodes.accessing.AbstractPointersObjectNodes.AbstractPointersObjectWriteNode;
import de.hpi.swa.trufflesqueak.nodes.accessing.SqueakObjectIdentityNode;
import de.hpi.swa.trufflesqueak.util.ObjectGraphUtils.ObjectTracer;

@ExportLibrary(InteropLibrary.class)
public final class PointersObject extends AbstractPointersObject {

public PointersObject(final SqueakImageContext image) {
Expand Down Expand Up @@ -187,4 +198,26 @@ public String toString() {
public void write(final SqueakImageWriter writer) {
super.writeHeaderAndLayoutObjects(writer);
}

/*
* INTEROPERABILITY
*/

@ExportMessage
protected static boolean isException(final PointersObject receiver,
@Shared("inheritsFromNode") @Cached final InheritsFromNode inheritsFromNode,
@CachedContext(SqueakLanguage.class) final SqueakImageContext image) {
return inheritsFromNode.execute(receiver, image.getExceptionClass());
}

@ExportMessage
protected static RuntimeException throwException(final PointersObject receiver,
@Shared("inheritsFromNode") @Cached final InheritsFromNode inheritsFromNode,
@CachedContext(SqueakLanguage.class) final SqueakImageContext image) throws UnsupportedMessageException {
if (isException(receiver, inheritsFromNode, image)) {
throw new SqueakInteropException(receiver);
} else {
throw UnsupportedMessageException.create();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
package de.hpi.swa.trufflesqueak.nodes;

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.Specialization;

import de.hpi.swa.trufflesqueak.model.ClassObject;
import de.hpi.swa.trufflesqueak.nodes.accessing.SqueakObjectClassNode;

@GenerateUncached
public abstract class InheritsFromNode extends AbstractNode {
protected static final int CACHE_SIZE = 3;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.List;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
Expand All @@ -19,21 +20,27 @@
import com.oracle.truffle.api.frame.FrameInstanceVisitor;
import com.oracle.truffle.api.nodes.NodeCost;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;

import de.hpi.swa.trufflesqueak.image.SqueakImageContext;
import de.hpi.swa.trufflesqueak.model.AbstractSqueakObject;
import de.hpi.swa.trufflesqueak.model.BooleanObject;
import de.hpi.swa.trufflesqueak.model.CompiledCodeObject;
import de.hpi.swa.trufflesqueak.model.CompiledMethodObject;
import de.hpi.swa.trufflesqueak.model.ContextObject;
import de.hpi.swa.trufflesqueak.model.FrameMarker;
import de.hpi.swa.trufflesqueak.model.NilObject;
import de.hpi.swa.trufflesqueak.model.layout.ObjectLayouts.CONTEXT;
import de.hpi.swa.trufflesqueak.nodes.accessing.ContextObjectNodes.ContextObjectReadNode;
import de.hpi.swa.trufflesqueak.nodes.accessing.ContextObjectNodes.ContextObjectWriteNode;
import de.hpi.swa.trufflesqueak.nodes.bytecodes.MiscellaneousBytecodes.CallPrimitiveNode;
import de.hpi.swa.trufflesqueak.nodes.primitives.AbstractPrimitiveFactoryHolder;
import de.hpi.swa.trufflesqueak.nodes.primitives.AbstractPrimitiveNode;
import de.hpi.swa.trufflesqueak.nodes.primitives.PrimitiveInterfaces.BinaryPrimitive;
import de.hpi.swa.trufflesqueak.nodes.primitives.PrimitiveInterfaces.TernaryPrimitive;
import de.hpi.swa.trufflesqueak.nodes.primitives.PrimitiveInterfaces.UnaryPrimitive;
import de.hpi.swa.trufflesqueak.nodes.primitives.SqueakPrimitive;
import de.hpi.swa.trufflesqueak.shared.SqueakLanguageConfig;
import de.hpi.swa.trufflesqueak.util.FrameAccess;

public class ContextPrimitives extends AbstractPrimitiveFactoryHolder {
Expand Down Expand Up @@ -192,6 +199,9 @@ private void terminateBetweenRecursively(final ContextObject start, final Contex
@GenerateNodeFactory
@SqueakPrimitive(indices = 197)
protected abstract static class PrimNextHandlerContextNode extends AbstractPrimitiveNode implements UnaryPrimitive {
private ContextObject cachedContext;

@TruffleBoundary
@Specialization(guards = {"receiver.hasMaterializedSender()"})
protected static final AbstractSqueakObject findNext(final ContextObject receiver) {
ContextObject context = receiver;
Expand All @@ -210,14 +220,25 @@ protected static final AbstractSqueakObject findNext(final ContextObject receive
}
}

@TruffleBoundary
@Specialization(guards = {"!receiver.hasMaterializedSender()"})
protected static final AbstractSqueakObject findNextAvoidingMaterialization(final ContextObject receiver) {
protected final AbstractSqueakObject findNextAvoidingMaterialization(final ContextObject receiver) {
final boolean[] foundMyself = new boolean[1];
final Object[] lastSender = new Object[1];
final ContextObject result = Truffle.getRuntime().iterateFrames(frameInstance -> {
final Frame current = frameInstance.getFrame(FrameInstance.FrameAccess.READ_ONLY);
if (!FrameAccess.isTruffleSqueakFrame(current)) {
return null; // Foreign frame cannot be handler.
final RootNode rootNode = ((RootCallTarget) frameInstance.getCallTarget()).getRootNode();
if (rootNode.isInternal() || rootNode.getLanguageInfo().getId().equals(SqueakLanguageConfig.ID)) {
/* Skip internal and all other nodes that belong to TruffleSqueak. */
return null;
} else {
/*
* Found a frame of another language. Stop here by returning the receiver
* context. This special case will be handled later on.
*/
return receiver;
}
}
final ContextObject context = FrameAccess.getContext(current);
if (!foundMyself[0]) {
Expand All @@ -237,7 +258,13 @@ protected static final AbstractSqueakObject findNextAvoidingMaterialization(fina
}
return null;
});
if (result == null) {
if (result == receiver) {
/*
* Foreign frame found during frame iteration. Inject a fake context which will
* throw the Smalltalk exception as polyglot exception.
*/
return getInteropExceptionThrowingContext();
} else if (result == null) {
if (!foundMyself[0]) {
return findNext(receiver); // Fallback to other version.
}
Expand All @@ -250,6 +277,35 @@ protected static final AbstractSqueakObject findNextAvoidingMaterialization(fina
return result;
}
}

/**
* Returns a fake context for BlockClosure>>#on:do: that handles any exception and rethrows
* it through Interop. This allows Smalltalk exceptions to be thrown to other languages, so
* that they can catch them. The mechanism works essentially like this:
*
* <pre>
* <code>[ ... ] on: Exception do: [ :e | Interop throwException: e exception ]</code>
* </pre>
*
* (see Context>>#handleSignal:)
*/
private ContextObject getInteropExceptionThrowingContext() {
if (cachedContext == null) {
final SqueakImageContext image = lookupContext();
assert image.evaluate("Interop") != NilObject.SINGLETON : "Interop class must be present";
final CompiledMethodObject onDoMethod = (CompiledMethodObject) image.evaluate("BlockClosure>>#on:do:");
cachedContext = ContextObject.create(image, onDoMethod.getSqueakContextSize());
cachedContext.setMethod(onDoMethod);
cachedContext.setReceiver(NilObject.SINGLETON);
cachedContext.atTempPut(0, image.evaluate("Exception"));
cachedContext.atTempPut(1, image.evaluate("[ :e | Interop throwException: e exception ]"));
cachedContext.atTempPut(2, BooleanObject.TRUE);
cachedContext.setInstructionPointer(CallPrimitiveNode.NUM_BYTECODES);
cachedContext.setStackPointer(onDoMethod.getNumTemps());
cachedContext.removeSender();
}
return cachedContext.shallowCopy();
}
}

@NodeInfo(cost = NodeCost.NONE)
Expand Down

0 comments on commit ddef572

Please sign in to comment.