package com.oracle.truffle.api.test.polyglot;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.PolyglotException.StackFrame;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyExecutable;
import org.junit.Assert;
import org.junit.Test;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.RootNode;
public class PolyglotExceptionTest extends AbstractPolyglotTest {
@SuppressWarnings("serial")
private static class TestGuestError extends AbstractTruffleException {
TestGuestError() {
super("MyError");
}
}
@Test
public void testExceptionWrapping() {
try (Context context1 = Context.create();
Context context2 = Context.create()) {
CauseErrorTruffleObject causeError = new CauseErrorTruffleObject();
causeError.thrownError = new TestGuestError();
Value throwError = context1.asValue(causeError);
Value throwErrorOtherContext = context2.asValue(causeError);
try {
throwError.execute();
Assert.fail();
} catch (PolyglotException e) {
Assert.assertEquals(e.getMessage(), "MyError");
Assert.assertTrue(e.isGuestException());
}
Value verifyError = context1.asValue(new ProxyExecutable() {
public Object execute(Value... arguments) {
try {
throwError.execute();
Assert.fail();
} catch (PolyglotException e) {
Assert.assertEquals(e.getMessage(), "MyError");
Assert.assertTrue(e.isGuestException());
throw e;
}
return null;
}
});
try {
verifyError.execute();
Assert.fail();
} catch (PolyglotException e) {
Assert.assertEquals(e.getMessage(), "MyError");
Assert.assertTrue(e.isGuestException());
}
Value verifyErrorOtherContext = context1.asValue(new ProxyExecutable() {
public Object execute(Value... arguments) {
try {
throwErrorOtherContext.execute();
} catch (PolyglotException e) {
Assert.assertEquals(e.getMessage(), "MyError");
Assert.assertTrue(e.isGuestException());
throw e;
}
return null;
}
});
try {
verifyErrorOtherContext.execute();
Assert.fail();
} catch (PolyglotException e) {
Assert.assertTrue(e.asHostException() instanceof PolyglotException);
PolyglotException polyglot = (PolyglotException) e.asHostException();
Assert.assertEquals(polyglot.getMessage(), "MyError");
Assert.assertTrue(polyglot.isGuestException());
}
}
}
@Test
public void testLanguageExceptionUnwrapping() {
setupEnv();
Value throwError = context.asValue(new ProxyExecutable() {
public Object execute(Value... arguments) {
throw new RuntimeException();
}
});
VerifyErrorTruffleObject checkErrorObj = new VerifyErrorTruffleObject();
Value checkError = context.asValue(checkErrorObj);
checkErrorObj.verifyError = (e) -> {
Assert.assertTrue(InteropLibrary.getUncached().isException(e));
Assert.assertEquals("HostException", e.getClass().getSimpleName());
};
Assert.assertTrue(checkError.execute(throwError).asBoolean());
}
@ExportLibrary(InteropLibrary.class)
static class CauseErrorTruffleObject implements TruffleObject {
RuntimeException thrownError;
@ExportMessage
boolean isExecutable() {
return true;
}
@ExportMessage
final Object execute(@SuppressWarnings("unused") Object[] arguments) {
throw thrownError;
}
}
@ExportLibrary(InteropLibrary.class)
static class VerifyErrorTruffleObject implements TruffleObject {
Consumer<Throwable> verifyError;
@ExportMessage
boolean isExecutable() {
return true;
}
@ExportMessage
final Object execute(Object[] arguments) {
Object arg = arguments[0];
try {
InteropLibrary.getFactory().getUncached().execute(arg);
Assert.fail();
} catch (Throwable e) {
verifyError.accept(e);
}
return true;
}
}
@Test
public void testRecursiveHostException() {
setupEnv();
RuntimeException e1 = new RuntimeException();
RuntimeException e2 = new RuntimeException();
e1.initCause(e2);
e2.initCause(e1);
Value throwError = context.asValue(new ProxyExecutable() {
public Object execute(Value... arguments) {
throw e1;
}
});
try {
throwError.execute();
fail();
} catch (PolyglotException e) {
assertSame(e1, e.asHostException());
}
}
@SuppressWarnings("serial")
public static class TruncatedException extends RuntimeException {
private final int limit;
public TruncatedException(int limit) {
assert limit >= 0;
this.limit = limit;
}
@Override
public StackTraceElement[] getStackTrace() {
StackTraceElement[] stackTrace = super.getStackTrace();
return Arrays.copyOf(stackTrace, Math.min(limit, stackTrace.length));
}
@SuppressWarnings("unused")
public static TruncatedException newTruncatedException(int limit) {
return new TruncatedException(limit);
}
public static TruncatedException createViaConstructorReflectively(int limit) {
try {
Constructor<TruncatedException> init = TruncatedException.class.getConstructor(int.class);
return init.newInstance(limit);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw new AssertionError(e);
}
}
public static TruncatedException createViaMethodReflectively(int limit) {
try {
Method init = TruncatedException.class.getDeclaredMethod("newTruncatedException", int.class);
return (TruncatedException) init.invoke(null, limit);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionError(e);
}
}
}
@SuppressWarnings("unused")
@Test
public void testTruncatedStackTrace() {
setupEnv();
Value throwError = context.asValue(new ProxyExecutable() {
public Object execute(Value... arguments) {
throw arguments[0].<RuntimeException> asHostObject();
}
});
for (int limit = 0; limit < 16; ++limit) {
for (TruncatedException ex : Arrays.asList(
TruncatedException.createViaMethodReflectively(limit),
TruncatedException.createViaConstructorReflectively(limit))) {
try {
throwError.execute(ex);
fail();
} catch (PolyglotException e) {
assertSame(ex, e.asHostException());
int frameCount = 0;
for (PolyglotException.StackFrame unused : e.getPolyglotStackTrace()) {
frameCount++;
}
assertTrue(ex.getStackTrace().length <= limit);
assertEquals(ex.getStackTrace().length, frameCount);
}
}
}
}
@SuppressWarnings("unused")
@Test
public void testGuestOOMResourceLimit() {
setupEnv(Context.create(), new ProxyLanguage() {
@Override
protected CallTarget parse(ParsingRequest request) throws Exception {
return Truffle.getRuntime().createCallTarget(new RootNode(getCurrentLanguage()) {
@Override
public Object execute(VirtualFrame frame) {
allocateALot();
return 42;
}
@TruffleBoundary
private void allocateALot() {
List<byte[]> blocks = new ArrayList<>();
while (true) {
blocks.add(new byte[1024 * 1024]);
}
}
@Override
public String getName() {
return "testRootName";
}
});
}
});
assertFails(() -> context.eval(ProxyLanguage.ID, "test"), PolyglotException.class, (e) -> {
assertTrue(e.isResourceExhausted());
assertFalse(e.isInternalError());
assertTrue(e.isGuestException());
assertFalse(e.isHostException());
assertFalse(e.isCancelled());
Iterator<StackFrame> iterator = e.getPolyglotStackTrace().iterator();
boolean foundFrame = false;
while (iterator.hasNext()) {
StackFrame frame = iterator.next();
if (frame.isGuestFrame()) {
foundFrame = true;
assertTrue(frame.isGuestFrame());
assertEquals("testRootName", frame.getRootName());
}
}
assertTrue(foundFrame);
});
}
@Test
public void testGuestStackOverflowResourceLimit() {
setupEnv(Context.create(), new ProxyLanguage() {
@Override
protected CallTarget parse(ParsingRequest request) throws Exception {
return Truffle.getRuntime().createCallTarget(new RootNode(getCurrentLanguage()) {
@Override
@TruffleBoundary
public Object execute(VirtualFrame frame) {
return execute(frame);
}
@Override
public String getName() {
return "testRootName";
}
});
}
});
assertFails(() -> context.eval(ProxyLanguage.ID, "test"), PolyglotException.class, (e) -> {
assertTrue(e.isResourceExhausted());
assertFalse(e.isInternalError());
assertTrue(e.isGuestException());
assertFalse(e.isHostException());
assertFalse(e.isCancelled());
Iterator<StackFrame> iterator = e.getPolyglotStackTrace().iterator();
boolean foundFrame = false;
while (iterator.hasNext()) {
StackFrame frame = iterator.next();
if (frame.isGuestFrame()) {
foundFrame = true;
assertTrue(frame.isGuestFrame());
assertEquals("testRootName", frame.getRootName());
}
}
assertTrue(foundFrame);
});
}
static class ThrowOOM implements Runnable {
public void run() {
List<byte[]> blocks = new ArrayList<>();
while (true) {
blocks.add(new byte[1024 * 1024]);
}
}
}
@Test
public void testHostOOMResourceLimit() {
setupEnv(Context.newBuilder().allowHostAccess(HostAccess.ALL).build());
Value v = context.asValue(new ThrowOOM());
assertFails(() -> v.execute(), PolyglotException.class, (e) -> {
assertTrue(e.isResourceExhausted());
assertFalse(e.isInternalError());
assertFalse(e.isGuestException());
assertTrue(e.isHostException());
assertFalse(e.isCancelled());
});
}
static class ThrowStackOverflow implements Runnable {
public void run() {
run();
}
}
@Test
public void testHostStackOverflowResourceLimit() {
setupEnv(Context.newBuilder().allowHostAccess(HostAccess.ALL).build());
Value v = context.asValue(new ThrowStackOverflow());
assertFails(() -> v.execute(), PolyglotException.class, (e) -> {
assertTrue(e.isResourceExhausted());
assertFalse(e.isInternalError());
assertFalse(e.isGuestException());
assertTrue(e.isHostException());
Iterator<StackFrame> iterator = e.getPolyglotStackTrace().iterator();
StackFrame frame = iterator.next();
assertTrue(frame.isHostFrame());
assertTrue(frame.getRootName(), frame.getRootName().endsWith("ThrowStackOverflow.run"));
frame = iterator.next();
assertTrue(frame.isHostFrame());
});
}
}