From 0bd7bc6ca95a93a668502c9f6253641a6e406a10 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 2 Oct 2024 19:27:43 +0200 Subject: [PATCH] wasm gc: fix support of legacy Object.cast method --- .../teavm/dependency/DependencyAnalyzer.java | 6 ++--- .../dependency/DependencyClassSource.java | 5 +++- .../teavm/dependency/ReferenceResolver.java | 21 +++++++++++------ .../org/teavm/backend/wasm/wasm-gc-runtime.js | 14 +++++++++-- .../src/main/java/org/teavm/jso/JSObject.java | 1 + .../jso/impl/JSObjectClassTransformer.java | 3 +++ .../teavm/jso/test/ClassWithConstructor.java | 3 +++ .../org/teavm/jso/test/ImportClassTest.java | 9 +++++++- .../jso/test/SubclassWithConstructor.java | 23 +++++++++++++++++++ .../teavm/jso/test/classWithConstructor.js | 14 +++++++++++ .../src/main/resources/test-server/frame.js | 4 +++- 11 files changed, 88 insertions(+), 15 deletions(-) create mode 100644 tests/src/test/java/org/teavm/jso/test/SubclassWithConstructor.java diff --git a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java index fa8921c9f..35397d61f 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java +++ b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java @@ -265,7 +265,7 @@ public void submitMethod(MethodReference methodRef, Program program) { dep.used = false; lock(dep, false); deferredTasks.add(() -> { - classSource.getReferenceResolver().use(dep.method.getReference(), diagnostics); + classSource.getReferenceResolver().use(dep.method.getReference()); processMethod(dep); dep.used = true; }); @@ -476,9 +476,9 @@ private MethodDependency createMethodDep(MethodReference methodRef, MethodHolder abstract DependencyNode createClassValueNode(int degree, DependencyNode parent); void scheduleMethodAnalysis(MethodDependency dep) { - classSource.getReferenceResolver().use(dep.getReference(), diagnostics); + classSource.getReferenceResolver().use(dep.getReference()); deferredTasks.add(() -> { - classSource.getReferenceResolver().use(dep.getReference(), diagnostics); + classSource.getReferenceResolver().use(dep.getReference()); processMethod(dep); }); } diff --git a/core/src/main/java/org/teavm/dependency/DependencyClassSource.java b/core/src/main/java/org/teavm/dependency/DependencyClassSource.java index 7b6c7984b..76557e725 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyClassSource.java +++ b/core/src/main/java/org/teavm/dependency/DependencyClassSource.java @@ -72,7 +72,7 @@ class DependencyClassSource implements ClassHolderSource { this.diagnostics = diagnostics; innerHierarchy = new ClassHierarchy(innerSource); this.dependencyRegistration = dependencyRegistration; - referenceResolver = new ReferenceResolver(this, platformTags); + referenceResolver = new ReferenceResolver(this, platformTags, diagnostics); classInitInsertion = new ClassInitInsertion(this); } @@ -125,6 +125,9 @@ private void transformClass(ClassHolder cls) { if (method.getProgram() != null) { var program = method.getProgram(); method.setProgramSupplier(m -> { + if (disposed) { + return null; + } referenceResolver.resolve(m, program); classInitInsertion.apply(m, program); return program; diff --git a/core/src/main/java/org/teavm/dependency/ReferenceResolver.java b/core/src/main/java/org/teavm/dependency/ReferenceResolver.java index 3afbe68f6..85cebdc8f 100644 --- a/core/src/main/java/org/teavm/dependency/ReferenceResolver.java +++ b/core/src/main/java/org/teavm/dependency/ReferenceResolver.java @@ -22,7 +22,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.function.Consumer; import org.teavm.diagnostics.Diagnostics; import org.teavm.interop.SupportedOn; import org.teavm.interop.UnsupportedOn; @@ -64,6 +63,7 @@ public class ReferenceResolver { private ClassReaderSource classSource; private MethodReference currentMethod; + private Diagnostics diagnostics; private Program program; private boolean modified; private List instructionsToAdd = new ArrayList<>(); @@ -71,20 +71,23 @@ public class ReferenceResolver { private Map>> methodCache = new HashMap<>(1000, 0.5f); private Set platformTags = new HashSet<>(); private UnreachableBasicBlockEliminator unreachableBlockEliminator; - private Map>> pendingErrors = new HashMap<>(); + private Map> pendingErrors = new HashMap<>(); + private Set usedMethods = new HashSet<>(); private boolean shouldStop; - public ReferenceResolver(ClassReaderSource classSource, String[] platformTags) { + public ReferenceResolver(ClassReaderSource classSource, String[] platformTags, Diagnostics diagnostics) { this.classSource = classSource; this.platformTags.addAll(List.of(platformTags)); unreachableBlockEliminator = new UnreachableBasicBlockEliminator(); + this.diagnostics = diagnostics; } - public void use(MethodReference method, Diagnostics diagnostics) { + public void use(MethodReference method) { + usedMethods.add(method); var errors = pendingErrors.remove(method); if (errors != null) { for (var error : errors) { - error.accept(diagnostics); + error.run(); } } } @@ -394,8 +397,12 @@ private void emitExceptionThrow(TextLocation location, String exceptionName, Str private void reportError(TextLocation location, String message, Object param) { var method = currentMethod; - pendingErrors.computeIfAbsent(method, k -> new ArrayList<>()).add(diagnostics -> - diagnostics.error(new CallLocation(method, location), message, param)); + if (usedMethods.contains(method)) { + diagnostics.error(new CallLocation(method, location), message, param); + } else { + pendingErrors.computeIfAbsent(method, k -> new ArrayList<>()).add(() -> + diagnostics.error(new CallLocation(method, location), message, param)); + } } private static class FieldWrapper { diff --git a/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js b/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js index 683090835..368e74d21 100644 --- a/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js +++ b/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js @@ -20,6 +20,9 @@ TeaVM.wasm = function() { let getGlobalName = function(name) { return eval(name); } + let setGlobalName = function(name, value) { + new Function("value", name + " = value;")(value); + } function defaults(imports) { dateImports(imports); @@ -146,6 +149,13 @@ TeaVM.wasm = function() { function isIdentifierPart(s) { return isIdentifierStart(s) || s >= '0' && s <= '9'; } + function setProperty(obj, prop, value) { + if (obj === null) { + setGlobalName(prop, value); + } else { + obj[prop] = value; + } + } imports.teavmJso = { emptyString: () => "", stringFromCharCode: code => String.fromCharCode(code), @@ -158,8 +168,8 @@ TeaVM.wasm = function() { wrapBoolean: value => !!value, getProperty: (obj, prop) => obj !== null ? obj[prop] : getGlobalName(prop), getPropertyPure: (obj, prop) => obj !== null ? obj[prop] : getGlobalName(prop), - setProperty: (obj, prop, value) => obj[prop] = value, - setPropertyPure: (obj, prop) => obj[prop] = value, + setProperty: setProperty, + setPropertyPure: setProperty, global: getGlobalName, createClass(name) { let fn = new Function( diff --git a/jso/core/src/main/java/org/teavm/jso/JSObject.java b/jso/core/src/main/java/org/teavm/jso/JSObject.java index a3f5d74b6..1ba50b307 100644 --- a/jso/core/src/main/java/org/teavm/jso/JSObject.java +++ b/jso/core/src/main/java/org/teavm/jso/JSObject.java @@ -60,6 +60,7 @@ */ public interface JSObject { @SuppressWarnings("unchecked") + @Deprecated default T cast() { return (T) this; } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java index fe83f0b05..162adf1b5 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java @@ -376,6 +376,9 @@ private void createExposedClass(ClassReader cls, ExposedClass exposedCls) { } private boolean isJavaScriptClass(ClassReader cls) { + if (typeHelper.isJavaScriptClass(cls.getName())) { + return true; + } if (cls.getParent() != null && typeHelper.isJavaScriptClass(cls.getParent())) { return true; } diff --git a/tests/src/test/java/org/teavm/jso/test/ClassWithConstructor.java b/tests/src/test/java/org/teavm/jso/test/ClassWithConstructor.java index 1539986e3..60e77392d 100644 --- a/tests/src/test/java/org/teavm/jso/test/ClassWithConstructor.java +++ b/tests/src/test/java/org/teavm/jso/test/ClassWithConstructor.java @@ -45,4 +45,7 @@ public ClassWithConstructor() { @JSTopLevel @JSProperty public static native void setTopLevelProperty(String value); + + @JSTopLevel + public static native JSObject createClass(boolean subclass); } diff --git a/tests/src/test/java/org/teavm/jso/test/ImportClassTest.java b/tests/src/test/java/org/teavm/jso/test/ImportClassTest.java index 23a1c020f..ed027dcac 100644 --- a/tests/src/test/java/org/teavm/jso/test/ImportClassTest.java +++ b/tests/src/test/java/org/teavm/jso/test/ImportClassTest.java @@ -30,7 +30,7 @@ @RunWith(TeaVMTestRunner.class) @SkipJVM -@OnlyPlatform(TestPlatform.JAVASCRIPT) +@OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC}) @EachTestCompiledSeparately public class ImportClassTest { @Test @@ -75,6 +75,13 @@ public void topLevel() { assertEquals("update2", ClassWithConstructor.getTopLevelProperty()); } + @Test + @AttachJavaScript("org/teavm/jso/test/classWithConstructor.js") + public void legacyCastMethod() { + SubclassWithConstructor o = ClassWithConstructor.createClass(true).cast(); + assertEquals("subclass", o.baz()); + } + @JSBody(script = "return {};") private static native O create(); diff --git a/tests/src/test/java/org/teavm/jso/test/SubclassWithConstructor.java b/tests/src/test/java/org/teavm/jso/test/SubclassWithConstructor.java new file mode 100644 index 000000000..1138f2773 --- /dev/null +++ b/tests/src/test/java/org/teavm/jso/test/SubclassWithConstructor.java @@ -0,0 +1,23 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.test; + +import org.teavm.jso.JSClass; + +@JSClass +public class SubclassWithConstructor extends ClassWithConstructor { + public native String baz(); +} diff --git a/tests/src/test/resources/org/teavm/jso/test/classWithConstructor.js b/tests/src/test/resources/org/teavm/jso/test/classWithConstructor.js index fd6181879..69c7e11d4 100644 --- a/tests/src/test/resources/org/teavm/jso/test/classWithConstructor.js +++ b/tests/src/test/resources/org/teavm/jso/test/classWithConstructor.js @@ -32,6 +32,20 @@ class ClassWithConstructor { } } +class SubclassWithConstructor extends ClassWithConstructor { + constructor(foo) { + super(foo); + } + + baz() { + return "subclass"; + } +} + +function createClass(subclass) { + return subclass ? new SubclassWithConstructor(23) : new ClassWithConstructor(42); +} + function topLevelFunction() { return "top level"; } diff --git a/tools/browser-runner/src/main/resources/test-server/frame.js b/tools/browser-runner/src/main/resources/test-server/frame.js index ffa0140b2..8f934171d 100644 --- a/tools/browser-runner/src/main/resources/test-server/frame.js +++ b/tools/browser-runner/src/main/resources/test-server/frame.js @@ -44,7 +44,9 @@ window.addEventListener("message", event => { case "WASM_GC": { const runtimeFile = request.file.path + "-runtime.js"; - appendFiles([{ path: runtimeFile, type: "regular" }], 0, () => { + const runtimeFileObj = { path: runtimeFile, type: "regular" }; + const files = request.additionalFiles ? [...request.additionalFiles, runtimeFileObj] : [runtimeFileObj] + appendFiles(files, 0, () => { launchWasmGCTest(request.file, request.argument, response => { event.source.postMessage(response, "*"); });