package com.oracle.truffle.api.test.host;
import static org.junit.Assert.fail;
import java.util.Collections;
import org.graalvm.polyglot.Value;
import org.junit.ComparisonFailure;
import org.junit.Test;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.test.polyglot.ProxyLanguage;
public class HostInteropErrorTest extends ProxyLanguageEnvTest {
private static final InteropLibrary INTEROP = InteropLibrary.getFactory().getUncached();
public static class MyHostObj {
public int field;
public final int finalField;
public MyHostObj(int a) {
this.finalField = a;
}
public void foo(@SuppressWarnings("unused") int a) {
fail("foo called");
}
public void cce(Object human) {
Value cannotCast = (Value) human;
cannotCast.invokeMember("hello");
}
@Override
public String toString() {
return "MyHostObj";
}
}
@Test
public void testHostMethodArityError() throws InteropException {
Object hostObj = env.asGuestValue(new MyHostObj(42));
Object foo = INTEROP.readMember(hostObj, "foo");
assertFails(() -> INTEROP.invokeMember(hostObj, "foo"), ArityException.class,
"Arity error - expected: 1 actual: 0");
assertFails(() -> INTEROP.execute(foo), ArityException.class,
"Arity error - expected: 1 actual: 0");
}
@Test
public void testHostMethodArgumentTypeError() throws InteropException {
Object hostObj = env.asGuestValue(new MyHostObj(42));
Object foo = INTEROP.readMember(hostObj, "foo");
assertFails(() -> INTEROP.invokeMember(hostObj, "foo", env.asGuestValue(Collections.emptyMap())), UnsupportedTypeException.class,
"Cannot convert '{}'(language: Java, type: java.util.Collections$EmptyMap) to Java type 'int': Invalid or lossy primitive coercion.");
assertFails(() -> INTEROP.execute(foo, env.asGuestValue(Collections.emptyMap())), UnsupportedTypeException.class,
"Cannot convert '{}'(language: Java, type: java.util.Collections$EmptyMap) to Java type 'int': Invalid or lossy primitive coercion.");
assertFails(() -> INTEROP.invokeMember(hostObj, "foo", env.asGuestValue(null)), UnsupportedTypeException.class,
"Cannot convert null value 'null'(language: Java) to Java type 'int'.");
assertFails(() -> INTEROP.execute(foo, env.asGuestValue(null)), UnsupportedTypeException.class,
"Cannot convert null value 'null'(language: Java) to Java type 'int'.");
assertFails(() -> INTEROP.invokeMember(hostObj, "foo", new OtherObject()), UnsupportedTypeException.class,
"Cannot convert 'Other'(language: proxyLanguage, type: OtherType) to Java type 'int': Invalid or lossy primitive coercion.");
assertFails(() -> INTEROP.execute(foo, new OtherObject()), UnsupportedTypeException.class,
"Cannot convert 'Other'(language: proxyLanguage, type: OtherType) to Java type 'int': Invalid or lossy primitive coercion.");
assertFails(() -> INTEROP.invokeMember(hostObj, "foo", new OtherNull()), UnsupportedTypeException.class,
"Cannot convert null value 'null'(language: proxyLanguage, type: Unknown) to Java type 'int'.");
assertFails(() -> INTEROP.execute(foo, new OtherNull()), UnsupportedTypeException.class,
"Cannot convert null value 'null'(language: proxyLanguage, type: Unknown) to Java type 'int'.");
}
@Test
public void testHostConstructorArgumentTypeError() {
Object hostClass = env.asHostSymbol(MyHostObj.class);
assertFails(() -> INTEROP.instantiate(hostClass, env.asGuestValue(Collections.emptyMap())), UnsupportedTypeException.class,
"Cannot convert '{}'(language: Java, type: java.util.Collections$EmptyMap) to Java type 'int': Invalid or lossy primitive coercion.");
assertFails(() -> INTEROP.instantiate(hostClass, env.asGuestValue(null)), UnsupportedTypeException.class,
"Cannot convert null value 'null'(language: Java) to Java type 'int'.");
assertFails(() -> INTEROP.instantiate(hostClass, new OtherObject()), UnsupportedTypeException.class,
"Cannot convert 'Other'(language: proxyLanguage, type: OtherType) to Java type 'int': Invalid or lossy primitive coercion.");
assertFails(() -> INTEROP.instantiate(hostClass, new OtherNull()), UnsupportedTypeException.class,
"Cannot convert null value 'null'(language: proxyLanguage, type: Unknown) to Java type 'int'.");
}
@Test
public void testHostArrayTypeError() {
Object hostArray = env.asGuestValue(new int[]{0, 0, 0, 0});
assertFails(() -> INTEROP.writeArrayElement(hostArray, 0, env.asGuestValue(Collections.emptyMap())), UnsupportedTypeException.class,
"Cannot convert '{}'(language: Java, type: java.util.Collections$EmptyMap) to Java type 'int': Invalid or lossy primitive coercion.");
assertFails(() -> INTEROP.writeArrayElement(hostArray, 0, env.asGuestValue(null)), UnsupportedTypeException.class,
"Cannot convert null value 'null'(language: Java) to Java type 'int'.");
assertFails(() -> INTEROP.writeArrayElement(hostArray, 0, new OtherObject()), UnsupportedTypeException.class,
"Cannot convert 'Other'(language: proxyLanguage, type: OtherType) to Java type 'int': Invalid or lossy primitive coercion.");
assertFails(() -> INTEROP.writeArrayElement(hostArray, 0, new OtherNull()), UnsupportedTypeException.class,
"Cannot convert null value 'null'(language: proxyLanguage, type: Unknown) to Java type 'int'.");
}
@Test
public void testHostFieldTypeError() {
Object hostObj = env.asGuestValue(new MyHostObj(42));
assertFails(() -> INTEROP.writeMember(hostObj, "field", env.asGuestValue(Collections.emptyMap())), UnsupportedTypeException.class,
"Cannot convert '{}'(language: Java, type: java.util.Collections$EmptyMap) to Java type 'int': Invalid or lossy primitive coercion.");
assertFails(() -> INTEROP.writeMember(hostObj, "field", env.asGuestValue(null)), UnsupportedTypeException.class,
"Cannot convert null value 'null'(language: Java) to Java type 'int'.");
assertFails(() -> INTEROP.writeMember(hostObj, "field", new OtherObject()), UnsupportedTypeException.class,
"Cannot convert 'Other'(language: proxyLanguage, type: OtherType) to Java type 'int': Invalid or lossy primitive coercion.");
assertFails(() -> INTEROP.writeMember(hostObj, "field", new OtherNull()), UnsupportedTypeException.class,
"Cannot convert null value 'null'(language: proxyLanguage, type: Unknown) to Java type 'int'.");
}
@Test
public void testHostFinalFieldError() {
Object hostObj = env.asGuestValue(new MyHostObj(42));
assertFails(() -> INTEROP.writeMember(hostObj, "finalField", 42), UnknownIdentifierException.class,
"Unknown identifier: finalField");
assertFails(() -> INTEROP.writeMember(hostObj, "finalField", env.asGuestValue(null)), UnknownIdentifierException.class,
"Unknown identifier: finalField");
assertFails(() -> INTEROP.writeMember(hostObj, "finalField", env.asGuestValue(Collections.emptyMap())), UnknownIdentifierException.class,
"Unknown identifier: finalField");
}
@Test
public void testClassCastExceptionInHostMethod() throws InteropException, ClassNotFoundException {
Object hostObj = env.asGuestValue(new MyHostObj(42));
Object foo = INTEROP.readMember(hostObj, "cce");
Class<? extends Exception> hostExceptionClass = Class.forName("com.oracle.truffle.polyglot.HostException").asSubclass(Exception.class);
assertFails(() -> INTEROP.invokeMember(hostObj, "cce", 42), hostExceptionClass, null);
assertFails(() -> INTEROP.execute(foo, 42), hostExceptionClass, null);
}
@ExportLibrary(InteropLibrary.class)
class OtherObject implements TruffleObject {
@ExportMessage
final boolean hasLanguage() {
return true;
}
@ExportMessage
final Class<? extends TruffleLanguage<?>> getLanguage() {
return ProxyLanguage.class;
}
@ExportMessage
final Object toDisplayString(@SuppressWarnings("unused") boolean allowSideEffects) {
return "Other";
}
@ExportMessage
final boolean hasMetaObject() {
return true;
}
@ExportMessage
final Object getMetaObject() {
return new OtherType();
}
}
@ExportLibrary(InteropLibrary.class)
class OtherNull implements TruffleObject {
@ExportMessage
final boolean isNull() {
return true;
}
@ExportMessage
final boolean hasLanguage() {
return true;
}
@ExportMessage
final Class<? extends TruffleLanguage<?>> getLanguage() {
return ProxyLanguage.class;
}
@ExportMessage
final Object toDisplayString(@SuppressWarnings("unused") boolean allowSideEffects) {
return "null";
}
}
@ExportLibrary(InteropLibrary.class)
class OtherType implements TruffleObject {
@ExportMessage
final boolean isMetaObject() {
return true;
}
@ExportMessage(name = "getMetaQualifiedName")
@ExportMessage(name = "getMetaSimpleName")
final Object getMetaName() {
return "OtherType";
}
@ExportMessage
final boolean isMetaInstance(Object instance) {
return instance instanceof OtherObject;
}
}
@FunctionalInterface
interface InteropRunnable {
void run() throws Exception;
}
private static void assertFails(InteropRunnable r, Class<?> hostExceptionType, String message) {
try {
r.run();
fail("No error but expected " + hostExceptionType);
} catch (Exception e) {
if (!hostExceptionType.isInstance(e)) {
fail(String.format("Expected %s: \"%s\" but got %s: \"%s\"", hostExceptionType.getName(), message, e.getClass().getName(), e.getMessage()));
}
if (message != null && !message.equals(e.getMessage())) {
ComparisonFailure f = new ComparisonFailure(e.getMessage(), message, e.getMessage());
f.initCause(e);
throw f;
}
}
}
}