From a1d711d0698eacd9bee8ab4a60fc6f69a84cd26a Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 16 Apr 2024 20:03:19 +0200 Subject: [PATCH] jso: when casting to JS objects, don't fail cast when cast object is null --- .../resources/org/teavm/backend/javascript/reflection.js | 1 + jso/impl/src/main/java/org/teavm/jso/impl/JS.java | 4 ++++ .../main/java/org/teavm/jso/impl/JSClassProcessor.java | 2 +- jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java | 2 ++ .../main/java/org/teavm/jso/impl/JSNativeInjector.java | 8 ++++++++ .../src/test/java/org/teavm/jso/test/InstanceOfTest.java | 9 ++++++++- 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/org/teavm/backend/javascript/reflection.js b/core/src/main/resources/org/teavm/backend/javascript/reflection.js index f3172ff67..c8cb67944 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/reflection.js +++ b/core/src/main/resources/org/teavm/backend/javascript/reflection.js @@ -44,3 +44,4 @@ let $rt_castToClass = (obj, cls) => { } return obj; } +let $rt_instanceOfOrNull = (obj, cls) => obj === null || obj instanceof cls; \ No newline at end of file diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java index 8c35d51f9..03ec6ff9d 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java @@ -709,6 +709,10 @@ final class JS { @NoSideEffects public static native boolean instanceOf(JSObject obj, JSObject cls); + @InjectedBy(JSNativeInjector.class) + @NoSideEffects + public static native boolean instanceOfOrNull(JSObject obj, JSObject cls); + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native boolean isPrimitive(JSObject obj, JSObject primitive); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java index df0d8fb3a..ba0b970a0 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java @@ -578,7 +578,7 @@ class JSClassProcessor { var primitiveType = getPrimitiveType(targetClassName); var invoke = new InvokeInstruction(); invoke.setType(InvocationType.SPECIAL); - invoke.setMethod(primitiveType != null ? JSMethods.IS_PRIMITIVE : JSMethods.INSTANCE_OF); + invoke.setMethod(primitiveType != null ? JSMethods.IS_PRIMITIVE : JSMethods.INSTANCE_OF_OR_NULL); var secondArg = primitiveType != null ? marshaller.addJsString(primitiveType, location) : marshaller.classRef(targetClassName, location); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java index aad726d40..1f0ade807 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java @@ -127,6 +127,8 @@ final class JSMethods { public static final MethodReference INSTANCE_OF = new MethodReference(JS.class, "instanceOf", JSObject.class, JSObject.class, boolean.class); + public static final MethodReference INSTANCE_OF_OR_NULL = new MethodReference(JS.class, "instanceOfOrNull", + JSObject.class, JSObject.class, boolean.class); public static final MethodReference IS_PRIMITIVE = new MethodReference(JS.class, "isPrimitive", JSObject.class, JSObject.class, boolean.class); public static final MethodReference THROW_CCE_IF_FALSE = new MethodReference(JS.class, "throwCCEIfFalse", diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeInjector.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeInjector.java index f3a30337d..511435faf 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeInjector.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeInjector.java @@ -215,6 +215,14 @@ public class JSNativeInjector implements Injector, DependencyPlugin { } break; } + case "instanceOfOrNull": { + writer.appendFunction("$rt_instanceOfOrNull").append("("); + context.writeExpr(context.getArgument(0), Precedence.min()); + writer.append(",").ws(); + context.writeExpr(context.getArgument(1), Precedence.min()); + writer.append(")"); + break; + } case "isPrimitive": { if (context.getPrecedence().ordinal() >= Precedence.CONDITIONAL.ordinal()) { writer.append("("); diff --git a/tests/src/test/java/org/teavm/jso/test/InstanceOfTest.java b/tests/src/test/java/org/teavm/jso/test/InstanceOfTest.java index 82b7bc1e5..d18e3f5cb 100644 --- a/tests/src/test/java/org/teavm/jso/test/InstanceOfTest.java +++ b/tests/src/test/java/org/teavm/jso/test/InstanceOfTest.java @@ -17,6 +17,7 @@ package org.teavm.jso.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Test; @@ -89,6 +90,9 @@ public class InstanceOfTest { } catch (ClassCastException e) { // expected } + + var d = (ClassWithConstructor) returnNull(); + assertNull(d); } private Object callCreateClassWithConstructor() { @@ -98,6 +102,9 @@ public class InstanceOfTest { @JSBody(script = "return new ClassWithConstructor();") private static native JSObject createClassWithConstructor(); + @JSBody(script = "return null;") + private static native JSObject returnNull(); + private Object callCreateNumber() { return createNumber(); } @@ -111,7 +118,7 @@ public class InstanceOfTest { } @JSClass(transparent = true) - abstract class C implements JSObject { + static abstract class C implements JSObject { @JSProperty public abstract int getFoo(); }