From f668e27daa7b04cf0650e5d141dbcf55f85c2388 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 1 Apr 2024 17:52:28 +0200 Subject: [PATCH] jso: properly support JSWrapper generation when java.lang.Object method is called with receiver type of JSObject interface; add optimization for JSObject.toString call. Fix #898 --- .../org/teavm/jso/impl/JSClassProcessor.java | 63 +++++++++++++++++-- .../org/teavm/jso/test/JSWrapperTest.java | 12 ++++ 2 files changed, 69 insertions(+), 6 deletions(-) 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 88eb8494b..d791979db 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 @@ -100,6 +100,7 @@ class JSClassProcessor { private JSValueMarshaller marshaller; private IncrementalDependencyRegistration incrementalCache; private JSImportAnnotationCache annotationCache; + private ClassReader objectClass; JSClassProcessor(ClassReaderSource classSource, JSTypeHelper typeHelper, JSBodyRepository repository, Diagnostics diagnostics, IncrementalDependencyRegistration incrementalCache) { @@ -286,13 +287,16 @@ class JSClassProcessor { processIsInstance((IsInstanceInstruction) insn); } else if (insn instanceof InvokeInstruction) { var invoke = (InvokeInstruction) insn; + var callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation()); + if (processToString(invoke, callLocation)) { + continue; + } var method = getMethod(invoke.getMethod().getClassName(), invoke.getMethod().getDescriptor()); + processInvokeArgs(invoke, method); if (method == null) { continue; } - processInvokeArgs(invoke, method); - var callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation()); replacement.clear(); if (processInvocation(method, callLocation, invoke, methodToProcess)) { insn.insertNextAll(replacement); @@ -337,10 +341,18 @@ class JSClassProcessor { } private void processInvokeArgs(InvokeInstruction invoke, MethodReader methodToInvoke) { - if (typeHelper.isJavaScriptClass(invoke.getMethod().getClassName()) - || methodToInvoke.getAnnotations().get(JSBody.class.getName()) != null) { + if (methodToInvoke != null && methodToInvoke.getAnnotations().get(JSBody.class.getName()) != null) { return; } + var className = invoke.getMethod().getClassName(); + if (typeHelper.isJavaScriptClass(invoke.getMethod().getClassName())) { + if (invoke.getMethod().getName().equals("") + || getObjectClass().getMethod(invoke.getMethod().getDescriptor()) == null) { + return; + } else { + className = "java.lang.Object"; + } + } Variable[] newArgs = null; for (var i = 0; i < invoke.getArguments().size(); ++i) { var type = invoke.getMethod().parameterType(i); @@ -358,8 +370,7 @@ class JSClassProcessor { } if (invoke.getInstance() != null) { - invoke.setInstance(wrapJsAsJava(invoke, invoke.getInstance(), - ValueType.object(invoke.getMethod().getClassName()))); + invoke.setInstance(wrapJsAsJava(invoke, invoke.getInstance(), ValueType.object(className))); } } @@ -546,6 +557,39 @@ class JSClassProcessor { return unwrap.getReceiver(); } + private boolean processToString(InvokeInstruction invoke, CallLocation location) { + if (!invoke.getMethod().getName().equals("toString") || !invoke.getArguments().isEmpty() + || invoke.getInstance() == null || !invoke.getMethod().getReturnType().isObject(String.class) + || types.typeOf(invoke.getInstance()) != JSType.JS) { + return false; + } + + replacement.clear(); + var methodName = marshaller.addStringWrap(marshaller.addString("toString", invoke.getLocation()), + invoke.getLocation()); + + var jsInvoke = new InvokeInstruction(); + jsInvoke.setType(InvocationType.SPECIAL); + jsInvoke.setMethod(JSMethods.invoke(0)); + jsInvoke.setReceiver(program.createVariable()); + jsInvoke.setLocation(invoke.getLocation()); + jsInvoke.setArguments(invoke.getInstance(), methodName); + replacement.add(jsInvoke); + + var assign = new AssignInstruction(); + assign.setAssignee(marshaller.unwrapReturnValue(location, jsInvoke.getReceiver(), + invoke.getMethod().getReturnType(), false, canBeOnlyJava(invoke.getReceiver()))); + assign.setReceiver(invoke.getReceiver()); + assign.setLocation(invoke.getLocation()); + replacement.add(assign); + + invoke.insertNextAll(replacement); + replacement.clear(); + invoke.delete(); + + return true; + } + private boolean processInvocation(MethodReader method, CallLocation callLocation, InvokeInstruction invoke, MethodHolder methodToProcess) { if (method.getAnnotations().get(JSBody.class.getName()) != null) { @@ -837,6 +881,13 @@ class JSClassProcessor { return true; } + private ClassReader getObjectClass() { + if (objectClass == null) { + objectClass = classSource.get("java.lang.Object"); + } + return objectClass; + } + private Variable getCallTarget(InvokeInstruction invoke) { return invoke.getInstance() != null ? invoke.getInstance() diff --git a/tests/src/test/java/org/teavm/jso/test/JSWrapperTest.java b/tests/src/test/java/org/teavm/jso/test/JSWrapperTest.java index 5067ee228..611360f35 100644 --- a/tests/src/test/java/org/teavm/jso/test/JSWrapperTest.java +++ b/tests/src/test/java/org/teavm/jso/test/JSWrapperTest.java @@ -321,6 +321,15 @@ public class JSWrapperTest { assertTrue(JSObjects.isUndefined(field1)); } + @Test + public void jsToString() { + var a = createWithToString("foo"); + assertEquals("foo", a.toString()); + + Object b = createWithToString("bar"); + assertEquals("bar", b.toString()); + } + private void callSetProperty(Object instance, Object o) { setProperty(instance, "foo", o); } @@ -401,4 +410,7 @@ public class JSWrapperTest { interface JArraySupplier extends JSObject { J[] get(); } + + @JSBody(params = "s", script = "return { toString: () => s };") + private static native JSObject createWithToString(String s); }