From 3282ae3b59a0f6635c040ee269f66809a775b8d2 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 9 Nov 2023 21:16:31 +0100 Subject: [PATCH] Refactor mechanism that resolves methods and fields parsed from bytecode and reports errors about missing items --- .../java/lang/TIllegalAccessError.java | 25 ++ .../backend/javascript/JavaScriptTarget.java | 26 +- .../teavm/dependency/DependencyAnalyzer.java | 18 +- .../dependency/DependencyAnalyzerFactory.java | 2 +- .../dependency/DependencyClassSource.java | 33 +- .../dependency/FastDependencyAnalyzer.java | 5 +- .../java/org/teavm/dependency/Linker.java | 72 ---- .../teavm/dependency/MethodDependency.java | 1 - .../dependency/PreciseDependencyAnalyzer.java | 5 +- .../teavm/dependency/ReferenceResolver.java | 408 ++++++++++++++++++ .../transformation/ClassInitInsertion.java | 12 +- .../model/util/MissingItemsProcessor.java | 333 -------------- core/src/main/java/org/teavm/vm/TeaVM.java | 11 +- .../AsyncLowLevelDependencyListener.java | 182 -------- .../platform/plugin/AsyncMethodProcessor.java | 132 +++++- .../teavm/platform/plugin/PlatformPlugin.java | 4 - 16 files changed, 611 insertions(+), 658 deletions(-) create mode 100644 classlib/src/main/java/org/teavm/classlib/java/lang/TIllegalAccessError.java create mode 100644 core/src/main/java/org/teavm/dependency/ReferenceResolver.java delete mode 100644 core/src/main/java/org/teavm/model/util/MissingItemsProcessor.java delete mode 100644 platform/src/main/java/org/teavm/platform/plugin/AsyncLowLevelDependencyListener.java diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TIllegalAccessError.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TIllegalAccessError.java new file mode 100644 index 000000000..7be63b79e --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TIllegalAccessError.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 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.classlib.java.lang; + +public class TIllegalAccessError extends TIncompatibleClassChangeError { + public TIllegalAccessError() { + } + + public TIllegalAccessError(String message) { + super(message); + } +} diff --git a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java index 641559d48..643aeff95 100644 --- a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java +++ b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java @@ -251,36 +251,12 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { dep.use(); dependencyAnalyzer.linkField(new FieldReference(String.class.getName(), "characters")); - dependencyAnalyzer.linkMethod(new MethodReference(String.class, "hashCode", int.class)) - .propagate(0, "java.lang.String") - .use(); - dependencyAnalyzer.linkMethod(new MethodReference(String.class, "equals", Object.class, boolean.class)) - .propagate(0, "java.lang.String") - .propagate(1, "java.lang.String") - .use(); - - dependencyAnalyzer.linkMethod(new MethodReference(Object.class, "clone", Object.class)); - MethodDependency exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference( - NoClassDefFoundError.class, "", String.class, void.class)); dep = dependencyAnalyzer.linkMethod(new MethodReference(Object.class, "toString", String.class)); dep.getVariable(0).propagate(dependencyAnalyzer.getType("java.lang.Object")); dep.use(); - exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NoClassDefFoundError.class.getName())); - exceptionCons.getVariable(1).propagate(stringType); - exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference(NoSuchFieldError.class, "", - String.class, void.class)); - exceptionCons.use(); - exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NoSuchFieldError.class.getName())); - exceptionCons.getVariable(1).propagate(stringType); - exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference(NoSuchMethodError.class, "", - String.class, void.class)); - exceptionCons.use(); - exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NoSuchMethodError.class.getName())); - exceptionCons.getVariable(1).propagate(stringType); - - exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference( + var exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference( RuntimeException.class, "", String.class, void.class)); exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(RuntimeException.class.getName())); exceptionCons.getVariable(1).propagate(stringType); diff --git a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java index 34e34d342..cb3f1c0e8 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java +++ b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java @@ -33,7 +33,6 @@ import java.util.Map; import java.util.Optional; import java.util.Queue; import java.util.Set; -import java.util.function.Function; import org.objectweb.asm.tree.ClassNode; import org.teavm.cache.IncrementalDependencyProvider; import org.teavm.cache.IncrementalDependencyRegistration; @@ -51,7 +50,6 @@ import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; -import org.teavm.model.FieldHolder; import org.teavm.model.FieldReader; import org.teavm.model.FieldReference; import org.teavm.model.Instruction; @@ -87,7 +85,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo { private ClassLoader classLoader; private Map>> methodReaderCache = new HashMap<>(1000, 0.5f); private Map implementationCache = new HashMap<>(); - private Function fieldReaderCache; private Map> methodCache = new HashMap<>(); private Set reachedMethods = new LinkedHashSet<>(); private Set readonlyReachedMethods = Collections.unmodifiableSet(reachedMethods); @@ -118,18 +115,17 @@ public abstract class DependencyAnalyzer implements DependencyInfo { DependencyType classType; DependencyAnalyzer(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services, - Diagnostics diagnostics, ReferenceCache referenceCache) { + Diagnostics diagnostics, ReferenceCache referenceCache, String[] platformTags) { this.unprocessedClassSource = classSource; this.diagnostics = diagnostics; this.referenceCache = referenceCache; - this.classSource = new DependencyClassSource(classSource, diagnostics, incrementalCache); + this.classSource = new DependencyClassSource(classSource, diagnostics, incrementalCache, platformTags); agentClassSource = this.classSource; classHierarchy = new ClassHierarchy(this.classSource); this.classLoader = classLoader; this.services = services; - fieldReaderCache = new CachedFunction<>(preimage -> this.classSource.resolveMutable(preimage)); fieldCache = new CachedFunction<>(preimage -> { - FieldReader field = fieldReaderCache.apply(preimage); + var field = this.classSource.getReferenceResolver().resolve(preimage); if (field != null && !field.getReference().equals(preimage)) { return fieldCache.apply(field.getReference()); } @@ -274,6 +270,7 @@ public abstract class DependencyAnalyzer implements DependencyInfo { lock(dep, false); deferredTasks.add(() -> { processInvokeDynamic(dep); + classSource.getReferenceResolver().use(dep.method.getReference(), diagnostics); processMethod(dep); dep.used = true; }); @@ -499,8 +496,10 @@ public abstract class DependencyAnalyzer implements DependencyInfo { abstract DependencyNode createClassValueNode(int degree, DependencyNode parent); void scheduleMethodAnalysis(MethodDependency dep) { + classSource.getReferenceResolver().use(dep.getReference(), diagnostics); deferredTasks.add(() -> { processInvokeDynamic(dep); + classSource.getReferenceResolver().use(dep.getReference(), diagnostics); processMethod(dep); }); } @@ -762,10 +761,8 @@ public abstract class DependencyAnalyzer implements DependencyInfo { } allNodes.clear(); - classSource.cleanup(); agent.cleanup(); listeners.clear(); - classSource.innerHierarchy = null; agentClassSource = classSourcePacker.pack(classSource, ClassClosureAnalyzer.build(classSource, new ArrayList<>(classSource.cache.keySet()))); @@ -773,9 +770,10 @@ public abstract class DependencyAnalyzer implements DependencyInfo { classHierarchy = new ClassHierarchy(agentClassSource); generatedClassNames.addAll(classSource.getGeneratedClassNames()); } + classSource.innerHierarchy = null; + classSource.dispose(); classSource = null; methodReaderCache = null; - fieldReaderCache = null; } public void cleanupTypes() { diff --git a/core/src/main/java/org/teavm/dependency/DependencyAnalyzerFactory.java b/core/src/main/java/org/teavm/dependency/DependencyAnalyzerFactory.java index d129790b8..b8933b9bd 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyAnalyzerFactory.java +++ b/core/src/main/java/org/teavm/dependency/DependencyAnalyzerFactory.java @@ -22,5 +22,5 @@ import org.teavm.model.ReferenceCache; public interface DependencyAnalyzerFactory { DependencyAnalyzer create(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services, - Diagnostics diagnostics, ReferenceCache referenceCache); + Diagnostics diagnostics, ReferenceCache referenceCache, String[] platformTags); } diff --git a/core/src/main/java/org/teavm/dependency/DependencyClassSource.java b/core/src/main/java/org/teavm/dependency/DependencyClassSource.java index 92f4fcc5d..c459efd8c 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyClassSource.java +++ b/core/src/main/java/org/teavm/dependency/DependencyClassSource.java @@ -32,6 +32,7 @@ import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; import org.teavm.model.MethodHolder; import org.teavm.model.optimization.UnreachableBasicBlockEliminator; +import org.teavm.model.transformation.ClassInitInsertion; import org.teavm.model.util.ModelUtils; class DependencyClassSource implements ClassHolderSource { @@ -44,13 +45,21 @@ class DependencyClassSource implements ClassHolderSource { boolean obfuscated; boolean strict; Map> cache = new LinkedHashMap<>(1000, 0.5f); + private ReferenceResolver referenceResolver; + private ClassInitInsertion classInitInsertion; DependencyClassSource(ClassReaderSource innerSource, Diagnostics diagnostics, - IncrementalDependencyRegistration dependencyRegistration) { + IncrementalDependencyRegistration dependencyRegistration, String[] platformTags) { this.innerSource = innerSource; this.diagnostics = diagnostics; innerHierarchy = new ClassHierarchy(innerSource); this.dependencyRegistration = dependencyRegistration; + referenceResolver = new ReferenceResolver(this, platformTags); + classInitInsertion = new ClassInitInsertion(this); + } + + public ReferenceResolver getReferenceResolver() { + return referenceResolver; } @Override @@ -75,10 +84,22 @@ class DependencyClassSource implements ClassHolderSource { } private ClassHolder findAndTransformClass(String name) { - ClassHolder cls = findClass(name); - if (cls != null && !transformers.isEmpty()) { - for (ClassHolderTransformer transformer : transformers) { - transformer.transformClass(cls, transformContext); + var cls = findClass(name); + if (cls != null) { + if (!transformers.isEmpty()) { + for (var transformer : transformers) { + transformer.transformClass(cls, transformContext); + } + } + for (var method : cls.getMethods()) { + if (method.getProgram() != null) { + var program = method.getProgram(); + method.setProgramSupplier(m -> { + referenceResolver.resolve(m, program); + classInitInsertion.apply(m, program); + return program; + }); + } } } return cls; @@ -108,7 +129,7 @@ class DependencyClassSource implements ClassHolderSource { transformers.add(transformer); } - public void cleanup() { + public void dispose() { transformers.clear(); } diff --git a/core/src/main/java/org/teavm/dependency/FastDependencyAnalyzer.java b/core/src/main/java/org/teavm/dependency/FastDependencyAnalyzer.java index ed2718b8e..503152aaa 100644 --- a/core/src/main/java/org/teavm/dependency/FastDependencyAnalyzer.java +++ b/core/src/main/java/org/teavm/dependency/FastDependencyAnalyzer.java @@ -40,8 +40,9 @@ public class FastDependencyAnalyzer extends DependencyAnalyzer { private Map subtypeNodes = new HashMap<>(); public FastDependencyAnalyzer(ClassReaderSource classSource, ClassLoader classLoader, - ServiceRepository services, Diagnostics diagnostics, ReferenceCache referenceCache) { - super(classSource, classLoader, services, diagnostics, referenceCache); + ServiceRepository services, Diagnostics diagnostics, ReferenceCache referenceCache, + String[] platformTags) { + super(classSource, classLoader, services, diagnostics, referenceCache, platformTags); instancesNode = new DependencyNode(this, null); classesNode = new DependencyNode(this, null); diff --git a/core/src/main/java/org/teavm/dependency/Linker.java b/core/src/main/java/org/teavm/dependency/Linker.java index e5ed36bde..2ea7a83f7 100644 --- a/core/src/main/java/org/teavm/dependency/Linker.java +++ b/core/src/main/java/org/teavm/dependency/Linker.java @@ -15,31 +15,18 @@ */ package org.teavm.dependency; -import org.teavm.model.AccessLevel; -import org.teavm.model.BasicBlock; import org.teavm.model.ClassHolder; -import org.teavm.model.ClassReader; import org.teavm.model.ElementModifier; import org.teavm.model.FieldHolder; import org.teavm.model.FieldReference; -import org.teavm.model.Instruction; import org.teavm.model.MethodHolder; -import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; -import org.teavm.model.Program; -import org.teavm.model.instructions.GetFieldInstruction; -import org.teavm.model.instructions.InvocationType; -import org.teavm.model.instructions.InvokeInstruction; -import org.teavm.model.instructions.PutFieldInstruction; -import org.teavm.model.transformation.ClassInitInsertion; public class Linker { private DependencyInfo dependency; - private ClassInitInsertion classInitInsertion; public Linker(DependencyInfo dependency) { this.dependency = dependency; - classInitInsertion = new ClassInitInsertion(dependency); } public void link(ClassHolder cls) { @@ -54,8 +41,6 @@ public class Linker { method.getModifiers().remove(ElementModifier.NATIVE); method.setProgram(null); } - } else if (method.getProgram() != null) { - link(method, method.getProgram()); } } for (FieldHolder field : cls.getFields().toArray(new FieldHolder[0])) { @@ -64,63 +49,6 @@ public class Linker { cls.removeField(field); } } - } - public void link(MethodReader method, Program program) { - for (int i = 0; i < program.basicBlockCount(); ++i) { - BasicBlock block = program.basicBlockAt(i); - for (Instruction insn : block) { - if (insn instanceof InvokeInstruction) { - InvokeInstruction invoke = (InvokeInstruction) insn; - MethodReference calledRef = invoke.getMethod(); - if (invoke.getType() == InvocationType.SPECIAL) { - MethodDependencyInfo linkedMethod = dependency.getMethodImplementation(calledRef); - if (linkedMethod != null) { - invoke.setMethod(linkedMethod.getReference()); - } - } else if (invoke.getType() == InvocationType.VIRTUAL) { - MethodDependencyInfo linkedMethod = dependency.getMethodImplementation(calledRef); - if (linkedMethod == null || linkedMethod.isMissing()) { - continue; - } - calledRef = linkedMethod.getReference(); - ClassReader cls = dependency.getClassSource().get(calledRef.getClassName()); - boolean isFinal = false; - if (cls != null) { - if (cls.hasModifier(ElementModifier.FINAL)) { - isFinal = true; - } else { - MethodReader calledMethod = cls.getMethod(calledRef.getDescriptor()); - if (calledMethod != null) { - if (calledMethod.hasModifier(ElementModifier.FINAL) - || calledMethod.getLevel() == AccessLevel.PRIVATE) { - isFinal = true; - } - } - } - } - if (isFinal) { - invoke.setType(InvocationType.SPECIAL); - invoke.setMethod(calledRef); - } - } - } else if (insn instanceof GetFieldInstruction) { - GetFieldInstruction getField = (GetFieldInstruction) insn; - FieldDependencyInfo linkedField = dependency.getField(getField.getField()); - if (linkedField != null) { - getField.setField(linkedField.getReference()); - } - - } else if (insn instanceof PutFieldInstruction) { - PutFieldInstruction putField = (PutFieldInstruction) insn; - FieldDependencyInfo linkedField = dependency.getField(putField.getField()); - if (linkedField != null) { - putField.setField(linkedField.getReference()); - } - } - } - } - - classInitInsertion.apply(program, method); } } diff --git a/core/src/main/java/org/teavm/dependency/MethodDependency.java b/core/src/main/java/org/teavm/dependency/MethodDependency.java index 19f18a985..710132bf7 100644 --- a/core/src/main/java/org/teavm/dependency/MethodDependency.java +++ b/core/src/main/java/org/teavm/dependency/MethodDependency.java @@ -181,7 +181,6 @@ public class MethodDependency implements MethodDependencyInfo { return external; } - void cleanup() { if (method != null) { present = true; diff --git a/core/src/main/java/org/teavm/dependency/PreciseDependencyAnalyzer.java b/core/src/main/java/org/teavm/dependency/PreciseDependencyAnalyzer.java index ee72807a3..a27513aaf 100644 --- a/core/src/main/java/org/teavm/dependency/PreciseDependencyAnalyzer.java +++ b/core/src/main/java/org/teavm/dependency/PreciseDependencyAnalyzer.java @@ -25,8 +25,9 @@ import org.teavm.model.ValueType; public class PreciseDependencyAnalyzer extends DependencyAnalyzer { public PreciseDependencyAnalyzer(ClassReaderSource classSource, ClassLoader classLoader, - ServiceRepository services, Diagnostics diagnostics, ReferenceCache referenceCache) { - super(classSource, classLoader, services, diagnostics, referenceCache); + ServiceRepository services, Diagnostics diagnostics, ReferenceCache referenceCache, + String[] platformTags) { + super(classSource, classLoader, services, diagnostics, referenceCache, platformTags); } @Override diff --git a/core/src/main/java/org/teavm/dependency/ReferenceResolver.java b/core/src/main/java/org/teavm/dependency/ReferenceResolver.java new file mode 100644 index 000000000..3afbe68f6 --- /dev/null +++ b/core/src/main/java/org/teavm/dependency/ReferenceResolver.java @@ -0,0 +1,408 @@ +/* + * Copyright 2023 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.dependency; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +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; +import org.teavm.model.AccessLevel; +import org.teavm.model.AnnotationContainerReader; +import org.teavm.model.AnnotationReader; +import org.teavm.model.AnnotationValue; +import org.teavm.model.CallLocation; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; +import org.teavm.model.FieldReader; +import org.teavm.model.FieldReference; +import org.teavm.model.Instruction; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.TextLocation; +import org.teavm.model.ValueType; +import org.teavm.model.instructions.AbstractInstructionVisitor; +import org.teavm.model.instructions.CastInstruction; +import org.teavm.model.instructions.ClassConstantInstruction; +import org.teavm.model.instructions.ConstructArrayInstruction; +import org.teavm.model.instructions.ConstructInstruction; +import org.teavm.model.instructions.ConstructMultiArrayInstruction; +import org.teavm.model.instructions.GetFieldInstruction; +import org.teavm.model.instructions.InitClassInstruction; +import org.teavm.model.instructions.InstructionVisitor; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.IsInstanceInstruction; +import org.teavm.model.instructions.PutFieldInstruction; +import org.teavm.model.instructions.RaiseInstruction; +import org.teavm.model.instructions.StringConstantInstruction; +import org.teavm.model.optimization.UnreachableBasicBlockEliminator; +import org.teavm.model.util.ProgramUtils; + +public class ReferenceResolver { + private ClassReaderSource classSource; + private MethodReference currentMethod; + private Program program; + private boolean modified; + private List instructionsToAdd = new ArrayList<>(); + private Map fieldCache = new HashMap<>(); + private Map>> methodCache = new HashMap<>(1000, 0.5f); + private Set platformTags = new HashSet<>(); + private UnreachableBasicBlockEliminator unreachableBlockEliminator; + private Map>> pendingErrors = new HashMap<>(); + private boolean shouldStop; + + public ReferenceResolver(ClassReaderSource classSource, String[] platformTags) { + this.classSource = classSource; + this.platformTags.addAll(List.of(platformTags)); + unreachableBlockEliminator = new UnreachableBasicBlockEliminator(); + } + + public void use(MethodReference method, Diagnostics diagnostics) { + var errors = pendingErrors.remove(method); + if (errors != null) { + for (var error : errors) { + error.accept(diagnostics); + } + } + } + + public Program resolve(MethodHolder method, Program program) { + this.currentMethod = method.getReference(); + this.program = program; + for (var block : program.getBasicBlocks()) { + shouldStop = false; + for (var insn : block) { + insn.acceptVisitor(visitor); + if (shouldStop) { + break; + } + } + } + if (modified) { + unreachableBlockEliminator.optimize(program); + modified = false; + } + this.program = null; + this.currentMethod = null; + return program; + } + + private InstructionVisitor visitor = new AbstractInstructionVisitor() { + @Override + public void visit(InvokeInstruction insn) { + if (!resolve(insn)) { + shouldStop = true; + } + } + + @Override + public void visit(GetFieldInstruction insn) { + if (!resolve(insn)) { + shouldStop = true; + } + } + + @Override + public void visit(PutFieldInstruction insn) { + if (!resolve(insn)) { + shouldStop = true; + } + } + + @Override + public void visit(CastInstruction insn) { + if (!checkType(insn, insn.getTargetType())) { + shouldStop = true; + } + } + + @Override + public void visit(IsInstanceInstruction insn) { + if (!checkType(insn, insn.getType())) { + shouldStop = true; + } + } + + @Override + public void visit(InitClassInstruction insn) { + if (!checkClass(insn, insn.getClassName())) { + shouldStop = true; + } + } + + @Override + public void visit(ConstructInstruction insn) { + if (!checkClass(insn, insn.getType())) { + shouldStop = true; + } + } + + @Override + public void visit(ConstructArrayInstruction insn) { + if (!checkType(insn, insn.getItemType())) { + shouldStop = true; + } + } + + @Override + public void visit(ConstructMultiArrayInstruction insn) { + if (!checkType(insn, insn.getItemType())) { + shouldStop = true; + } + } + + @Override + public void visit(ClassConstantInstruction insn) { + if (!checkType(insn, insn.getConstant())) { + shouldStop = true; + } + } + }; + + private boolean resolve(InvokeInstruction instruction) { + var calledRef = instruction.getMethod(); + + if (!checkClass(instruction, calledRef.getClassName())) { + return false; + } + + if (instruction.getType() == InvocationType.SPECIAL) { + return resolveSpecial(instruction, calledRef); + } else { + return resolveVirtual(instruction, calledRef); + } + } + + private boolean resolve(GetFieldInstruction instruction) { + var resolvedField = resolve(instruction.getField()); + if (resolvedField == null) { + reportError(instruction.getLocation(), "Field {{f0}} was not found", instruction.getField()); + emitExceptionThrow(instruction.getLocation(), NoSuchFieldError.class.getName(), + "Field not found: " + instruction.getField()); + truncateBlock(instruction); + return false; + } + instruction.setField(resolvedField.getReference()); + return true; + } + + private boolean resolve(PutFieldInstruction instruction) { + var resolvedField = resolve(instruction.getField()); + if (resolvedField == null) { + reportError(instruction.getLocation(), "Field {{f0}} was not found", instruction.getField()); + emitExceptionThrow(instruction.getLocation(), NoSuchFieldError.class.getName(), + "Field not found: " + instruction.getField()); + truncateBlock(instruction); + return false; + } + instruction.setField(resolvedField.getReference()); + return true; + } + + private boolean resolveSpecial(InvokeInstruction instruction, MethodReference calledRef) { + var resolvedMethod = resolve(calledRef); + if (resolvedMethod == null) { + reportError(instruction.getLocation(), "Method {{m0}} was not found", calledRef); + emitExceptionThrow(instruction.getLocation(), NoSuchMethodError.class.getName(), + "Method not found: " + instruction.getMethod()); + truncateBlock(instruction); + return false; + } + if (!checkMethod(instruction, resolvedMethod.getReference())) { + return false; + } + + instruction.setMethod(resolvedMethod.getReference()); + return true; + } + + private boolean resolveVirtual(InvokeInstruction instruction, MethodReference calledRef) { + var resolvedMethod = resolve(calledRef); + if (resolvedMethod == null) { + reportError(instruction.getLocation(), "Method {{m0}} was not found", calledRef); + emitExceptionThrow(instruction.getLocation(), NoSuchMethodError.class.getName(), + "Method not found: " + instruction.getMethod()); + truncateBlock(instruction); + return false; + } + + if (!checkMethod(instruction, resolvedMethod.getReference())) { + return false; + } + + boolean isFinal = false; + if (resolvedMethod.hasModifier(ElementModifier.FINAL) + || resolvedMethod.getLevel() == AccessLevel.PRIVATE) { + isFinal = true; + } else { + var cls = classSource.get(resolvedMethod.getOwnerName()); + if (cls.hasModifier(ElementModifier.FINAL)) { + isFinal = true; + } + } + if (isFinal) { + instruction.setType(InvocationType.SPECIAL); + instruction.setMethod(resolvedMethod.getReference()); + } + return true; + } + + public FieldReader resolve(FieldReference field) { + return fieldCache.computeIfAbsent(field, f -> { + var result = classSource.resolve(f); + return new FieldWrapper(result); + }).value; + } + + private boolean checkType(Instruction instruction, ValueType type) { + while (type instanceof ValueType.Array) { + type = ((ValueType.Array) type).getItemType(); + } + if (type instanceof ValueType.Object) { + return checkClass(instruction, ((ValueType.Object) type).getClassName()); + } + return true; + } + + private boolean checkClass(Instruction instruction, String className) { + var cls = classSource.get(className); + if (cls == null) { + reportError(instruction.getLocation(), "Class {{c0}} was not found", className); + emitExceptionThrow(instruction.getLocation(), NoClassDefFoundError.class.getName(), + "Class not found: " + className); + truncateBlock(instruction); + return false; + } + if (!checkPlatformSupported(cls.getAnnotations())) { + reportError(instruction.getLocation(), "Class {{c0}} is not supported on current target", className); + emitExceptionThrow(instruction.getLocation(), NoClassDefFoundError.class.getName(), + "Class not found: " + className); + truncateBlock(instruction); + return false; + } + return true; + } + + private boolean checkMethod(Instruction instruction, MethodReference methodRef) { + var cls = classSource.get(methodRef.getClassName()); + if (cls != null) { + var methodReader = cls.getMethod(methodRef.getDescriptor()); + if (methodReader != null && !checkPlatformSupported(methodReader.getAnnotations())) { + reportError(instruction.getLocation(), "Method {{m0}} is not supported on current target", methodRef); + emitExceptionThrow(instruction.getLocation(), NoSuchMethodError.class.getName(), + "Method not found: " + methodRef); + truncateBlock(instruction); + return false; + } + } + return true; + } + + private boolean checkPlatformSupported(AnnotationContainerReader annotations) { + AnnotationReader supportedAnnot = annotations.get(SupportedOn.class.getName()); + AnnotationReader unsupportedAnnot = annotations.get(UnsupportedOn.class.getName()); + if (supportedAnnot != null) { + for (AnnotationValue value : supportedAnnot.getValue("value").getList()) { + if (platformTags.contains(value.getString())) { + return true; + } + } + return false; + } + if (unsupportedAnnot != null) { + for (AnnotationValue value : unsupportedAnnot.getValue("value").getList()) { + if (platformTags.contains(value.getString())) { + return false; + } + } + return true; + } + return true; + } + + public MethodReader resolve(MethodReference method) { + return methodCache + .computeIfAbsent(method.getClassName(), k -> new HashMap<>(100, 0.5f)) + .computeIfAbsent(method.getDescriptor(), k -> { + var methodReader = classSource.resolve(method); + return Optional.ofNullable(methodReader); + }) + .orElse(null); + } + + + private void truncateBlock(Instruction instruction) { + modified = true; + ProgramUtils.truncateBlock(instruction); + instruction.insertNextAll(instructionsToAdd); + instructionsToAdd.clear(); + instruction.delete(); + } + + private void emitExceptionThrow(TextLocation location, String exceptionName, String text) { + var exceptionVar = program.createVariable(); + var newExceptionInsn = new ConstructInstruction(); + newExceptionInsn.setType(exceptionName); + newExceptionInsn.setReceiver(exceptionVar); + newExceptionInsn.setLocation(location); + instructionsToAdd.add(newExceptionInsn); + + var constVar = program.createVariable(); + var constInsn = new StringConstantInstruction(); + constInsn.setConstant(text); + constInsn.setReceiver(constVar); + constInsn.setLocation(location); + instructionsToAdd.add(constInsn); + + var initExceptionInsn = new InvokeInstruction(); + initExceptionInsn.setInstance(exceptionVar); + initExceptionInsn.setMethod(new MethodReference(exceptionName, "", ValueType.object("java.lang.String"), + ValueType.VOID)); + initExceptionInsn.setType(InvocationType.SPECIAL); + initExceptionInsn.setArguments(constVar); + initExceptionInsn.setLocation(location); + instructionsToAdd.add(initExceptionInsn); + + var raiseInsn = new RaiseInstruction(); + raiseInsn.setException(exceptionVar); + raiseInsn.setLocation(location); + instructionsToAdd.add(raiseInsn); + } + + 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)); + } + + private static class FieldWrapper { + final FieldReader value; + + FieldWrapper(FieldReader value) { + this.value = value; + } + } +} diff --git a/core/src/main/java/org/teavm/model/transformation/ClassInitInsertion.java b/core/src/main/java/org/teavm/model/transformation/ClassInitInsertion.java index 3d23b73fd..11fc677f9 100644 --- a/core/src/main/java/org/teavm/model/transformation/ClassInitInsertion.java +++ b/core/src/main/java/org/teavm/model/transformation/ClassInitInsertion.java @@ -17,9 +17,9 @@ package org.teavm.model.transformation; import java.util.HashSet; import java.util.Set; -import org.teavm.dependency.DependencyInfo; import org.teavm.model.BasicBlock; import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.Instruction; import org.teavm.model.MethodDescriptor; @@ -36,13 +36,13 @@ import org.teavm.model.util.DominatorWalkerContext; public class ClassInitInsertion { private static final MethodDescriptor CLINIT = new MethodDescriptor("", void.class); - private DependencyInfo dependencyInfo; + private ClassReaderSource classSource; - public ClassInitInsertion(DependencyInfo dependencyInfo) { - this.dependencyInfo = dependencyInfo; + public ClassInitInsertion(ClassReaderSource classSource) { + this.classSource = classSource; } - public void apply(Program program, MethodReader method) { + public void apply(MethodReader method, Program program) { if (program.basicBlockCount() == 0) { return; } @@ -127,7 +127,7 @@ public class ClassInitInsertion { private void initializeClass(String className, Instruction instruction) { if (markClassAsInitialized(className)) { - ClassReader cls = dependencyInfo.getClassSource().get(className); + ClassReader cls = classSource.get(className); if (cls == null || cls.getMethod(CLINIT) != null) { InitClassInstruction initInsn = new InitClassInstruction(); initInsn.setClassName(className); diff --git a/core/src/main/java/org/teavm/model/util/MissingItemsProcessor.java b/core/src/main/java/org/teavm/model/util/MissingItemsProcessor.java deleted file mode 100644 index b2109fc5a..000000000 --- a/core/src/main/java/org/teavm/model/util/MissingItemsProcessor.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright 2015 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.model.util; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import org.teavm.dependency.DependencyInfo; -import org.teavm.dependency.MethodDependencyInfo; -import org.teavm.diagnostics.Diagnostics; -import org.teavm.interop.SupportedOn; -import org.teavm.interop.UnsupportedOn; -import org.teavm.model.AnnotationContainerReader; -import org.teavm.model.AnnotationReader; -import org.teavm.model.AnnotationValue; -import org.teavm.model.BasicBlock; -import org.teavm.model.CallLocation; -import org.teavm.model.ClassHierarchy; -import org.teavm.model.ClassHolder; -import org.teavm.model.ClassReader; -import org.teavm.model.FieldReference; -import org.teavm.model.Instruction; -import org.teavm.model.MethodHolder; -import org.teavm.model.MethodReader; -import org.teavm.model.MethodReference; -import org.teavm.model.Program; -import org.teavm.model.TextLocation; -import org.teavm.model.ValueType; -import org.teavm.model.Variable; -import org.teavm.model.instructions.AbstractInstructionVisitor; -import org.teavm.model.instructions.CastInstruction; -import org.teavm.model.instructions.ClassConstantInstruction; -import org.teavm.model.instructions.ConstructArrayInstruction; -import org.teavm.model.instructions.ConstructInstruction; -import org.teavm.model.instructions.ConstructMultiArrayInstruction; -import org.teavm.model.instructions.GetFieldInstruction; -import org.teavm.model.instructions.InitClassInstruction; -import org.teavm.model.instructions.InstructionVisitor; -import org.teavm.model.instructions.InvocationType; -import org.teavm.model.instructions.InvokeInstruction; -import org.teavm.model.instructions.IsInstanceInstruction; -import org.teavm.model.instructions.PutFieldInstruction; -import org.teavm.model.instructions.RaiseInstruction; -import org.teavm.model.instructions.StringConstantInstruction; -import org.teavm.model.optimization.UnreachableBasicBlockEliminator; - -public class MissingItemsProcessor { - private DependencyInfo dependencyInfo; - private ClassHierarchy hierarchy; - private Diagnostics diagnostics; - private List instructionsToAdd = new ArrayList<>(); - private MethodReference methodRef; - private Program program; - private Collection reachableClasses; - private Collection reachableMethods; - private Collection reachableFields; - private Set platformTags = new HashSet<>(); - - public MissingItemsProcessor(DependencyInfo dependencyInfo, ClassHierarchy hierarchy, Diagnostics diagnostics, - String[] platformTags) { - this.dependencyInfo = dependencyInfo; - this.diagnostics = diagnostics; - this.hierarchy = hierarchy; - reachableClasses = dependencyInfo.getReachableClasses(); - reachableMethods = dependencyInfo.getReachableMethods(); - reachableFields = dependencyInfo.getReachableFields(); - this.platformTags.addAll(Arrays.asList(platformTags)); - } - - public void processClass(ClassHolder cls) { - for (var method : cls.getMethods()) { - if (reachableMethods.contains(method.getReference()) && method.getProgram() != null) { - var methodDep = dependencyInfo.getMethod(method.getReference()); - if (methodDep != null && methodDep.isUsed()) { - processMethod(method); - } - } - } - } - - public void processMethod(MethodHolder method) { - processMethod(method.getReference(), method.getProgram()); - } - - public void processMethod(MethodReference method, Program program) { - this.methodRef = method; - this.program = program; - boolean wasModified = false; - for (int i = 0; i < program.basicBlockCount(); ++i) { - BasicBlock block = program.basicBlockAt(i); - instructionsToAdd.clear(); - boolean missing = false; - for (var insn : block) { - insn.acceptVisitor(instructionProcessor); - if (!instructionsToAdd.isEmpty()) { - wasModified = true; - truncateBlock(insn); - missing = true; - break; - } - } - if (!missing) { - for (var tryCatch : block.getTryCatchBlocks()) { - checkClass(null, tryCatch.getExceptionType()); - } - } - } - if (wasModified) { - new UnreachableBasicBlockEliminator().optimize(program); - } - } - - private void truncateBlock(Instruction instruction) { - ProgramUtils.truncateBlock(instruction); - instruction.insertNextAll(instructionsToAdd); - instruction.delete(); - } - - private void emitExceptionThrow(TextLocation location, String exceptionName, String text) { - Variable exceptionVar = program.createVariable(); - ConstructInstruction newExceptionInsn = new ConstructInstruction(); - newExceptionInsn.setType(exceptionName); - newExceptionInsn.setReceiver(exceptionVar); - newExceptionInsn.setLocation(location); - instructionsToAdd.add(newExceptionInsn); - - Variable constVar = program.createVariable(); - StringConstantInstruction constInsn = new StringConstantInstruction(); - constInsn.setConstant(text); - constInsn.setReceiver(constVar); - constInsn.setLocation(location); - instructionsToAdd.add(constInsn); - - InvokeInstruction initExceptionInsn = new InvokeInstruction(); - initExceptionInsn.setInstance(exceptionVar); - initExceptionInsn.setMethod(new MethodReference(exceptionName, "", ValueType.object("java.lang.String"), - ValueType.VOID)); - initExceptionInsn.setType(InvocationType.SPECIAL); - initExceptionInsn.setArguments(constVar); - initExceptionInsn.setLocation(location); - instructionsToAdd.add(initExceptionInsn); - - RaiseInstruction raiseInsn = new RaiseInstruction(); - raiseInsn.setException(exceptionVar); - raiseInsn.setLocation(location); - instructionsToAdd.add(raiseInsn); - } - - private boolean checkClass(TextLocation location, String className) { - if (!reachableClasses.contains(className)) { - return true; - } - - if (!dependencyInfo.getClass(className).isMissing()) { - ClassReader cls = dependencyInfo.getClassSource().get(className); - if (cls != null && !checkPlatformSupported(cls.getAnnotations())) { - diagnostics.error(new CallLocation(methodRef, location), "Class {{c0}} is not supported on " - + "current target", className); - emitExceptionThrow(location, NoClassDefFoundError.class.getName(), "Class not found: " + className); - return false; - } - return true; - } - - diagnostics.error(new CallLocation(methodRef, location), "Class {{c0}} was not found", - className); - emitExceptionThrow(location, NoClassDefFoundError.class.getName(), "Class not found: " + className); - return false; - } - - private boolean checkClass(TextLocation location, ValueType type) { - while (type instanceof ValueType.Array) { - type = ((ValueType.Array) type).getItemType(); - } - if (type instanceof ValueType.Object) { - return checkClass(location, ((ValueType.Object) type).getClassName()); - } - return true; - } - - private boolean checkMethod(TextLocation location, MethodReference method) { - if (!checkClass(location, method.getClassName())) { - return false; - } - if (!reachableMethods.contains(method)) { - return true; - } - MethodDependencyInfo methodDep = dependencyInfo.getMethod(method); - if (!methodDep.isUsed()) { - return true; - } - - if (!methodDep.isMissing()) { - ClassReader cls = dependencyInfo.getClassSource().get(method.getClassName()); - if (cls != null) { - MethodReader methodReader = cls.getMethod(method.getDescriptor()); - if (methodReader != null && !checkPlatformSupported(methodReader.getAnnotations())) { - diagnostics.error(new CallLocation(methodRef, location), "Method {{m0}} is not supported on " - + "current target", method); - emitExceptionThrow(location, NoSuchMethodError.class.getName(), "Method not found: " + method); - return false; - } - } - return true; - } - - diagnostics.error(new CallLocation(methodRef, location), "Method {{m0}} was not found", - method); - emitExceptionThrow(location, NoSuchMethodError.class.getName(), "Method not found: " + method); - return false; - } - - private boolean checkVirtualMethod(TextLocation location, MethodReference method) { - if (!checkClass(location, method.getClassName())) { - return false; - } - - if (hierarchy.resolve(method) != null) { - return true; - } - - diagnostics.error(new CallLocation(methodRef, location), "Method {{m0}} was not found", method); - emitExceptionThrow(location, NoSuchMethodError.class.getName(), "Method not found: " + method); - return true; - } - - private boolean checkField(TextLocation location, FieldReference field) { - if (!checkClass(location, field.getClassName())) { - return false; - } - if (!reachableFields.contains(field) || !dependencyInfo.getField(field).isMissing()) { - return true; - } - diagnostics.error(new CallLocation(methodRef, location), "Field {{f0}} was not found", - field); - emitExceptionThrow(location, NoSuchFieldError.class.getName(), "Field not found: " + field); - return true; - } - - private boolean checkPlatformSupported(AnnotationContainerReader annotations) { - AnnotationReader supportedAnnot = annotations.get(SupportedOn.class.getName()); - AnnotationReader unsupportedAnnot = annotations.get(UnsupportedOn.class.getName()); - if (supportedAnnot != null) { - for (AnnotationValue value : supportedAnnot.getValue("value").getList()) { - if (platformTags.contains(value.getString())) { - return true; - } - } - return false; - } - if (unsupportedAnnot != null) { - for (AnnotationValue value : unsupportedAnnot.getValue("value").getList()) { - if (platformTags.contains(value.getString())) { - return false; - } - } - return true; - } - return true; - } - - private InstructionVisitor instructionProcessor = new AbstractInstructionVisitor() { - @Override - public void visit(InitClassInstruction insn) { - checkClass(insn.getLocation(), insn.getClassName()); - } - - @Override - public void visit(IsInstanceInstruction insn) { - checkClass(insn.getLocation(), insn.getType()); - } - - @Override - public void visit(InvokeInstruction insn) { - if (insn.getType() != InvocationType.VIRTUAL) { - checkMethod(insn.getLocation(), insn.getMethod()); - } else { - checkVirtualMethod(insn.getLocation(), insn.getMethod()); - } - } - - @Override - public void visit(PutFieldInstruction insn) { - checkField(insn.getLocation(), insn.getField()); - } - - @Override - public void visit(GetFieldInstruction insn) { - checkField(insn.getLocation(), insn.getField()); - } - - @Override - public void visit(ConstructMultiArrayInstruction insn) { - checkClass(insn.getLocation(), insn.getItemType()); - } - - @Override - public void visit(ConstructInstruction insn) { - checkClass(insn.getLocation(), insn.getType()); - } - - @Override - public void visit(ConstructArrayInstruction insn) { - checkClass(insn.getLocation(), insn.getItemType()); - } - - @Override - public void visit(CastInstruction insn) { - checkClass(insn.getLocation(), insn.getTargetType()); - } - - @Override - public void visit(ClassConstantInstruction insn) { - checkClass(insn.getLocation(), insn.getConstant()); - } - }; -} diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index af9d45e00..d3dd81017 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -95,7 +95,6 @@ import org.teavm.model.optimization.UnreachableBasicBlockElimination; import org.teavm.model.optimization.UnusedVariableElimination; import org.teavm.model.text.ListingBuilder; import org.teavm.model.transformation.ClassInitializerInsertionTransformer; -import org.teavm.model.util.MissingItemsProcessor; import org.teavm.model.util.ModelUtils; import org.teavm.model.util.ProgramUtils; import org.teavm.model.util.RegisterAllocator; @@ -170,7 +169,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { classLoader = builder.classLoader; classSourcePacker = builder.classSourcePacker; dependencyAnalyzer = builder.dependencyAnalyzerFactory.create(builder.classSource, classLoader, - this, diagnostics, builder.referenceCache); + this, diagnostics, builder.referenceCache, target.getPlatformTags()); dependencyAnalyzer.setObfuscated(builder.obfuscated); dependencyAnalyzer.setStrict(builder.strict); progressListener = new TeaVMProgressListener() { @@ -569,8 +568,6 @@ public class TeaVM implements TeaVMHost, ServiceRepository { public ListableClassHolderSource link(DependencyAnalyzer dependency) { Linker linker = new Linker(dependency); var cutClasses = new MutableClassHolderSource(); - var missingItemsProcessor = new MissingItemsProcessor(dependency, - dependency.getClassHierarchy(), diagnostics, target.getPlatformTags()); if (wasCancelled()) { return cutClasses; } @@ -584,7 +581,6 @@ public class TeaVM implements TeaVMHost, ServiceRepository { if (clsReader != null) { ClassHolder cls = ModelUtils.copyClass(clsReader); cutClasses.putClassHolder(cls); - missingItemsProcessor.processClass(cls); linker.link(cls); } reportCompileProgress(++compileProgressValue); @@ -962,8 +958,6 @@ public class TeaVM implements TeaVMHost, ServiceRepository { class PostProcessingClassHolderSource implements ListableClassHolderSource { private Linker linker = new Linker(dependencyAnalyzer); - private MissingItemsProcessor missingItemsProcessor = new MissingItemsProcessor(dependencyAnalyzer, - dependencyAnalyzer.getClassHierarchy(), diagnostics, target.getPlatformTags()); private Map cache = new HashMap<>(); private Set classNames = Collections.unmodifiableSet(new HashSet<>( dependencyAnalyzer.getReachableClasses().stream() @@ -980,6 +974,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return null; } ClassHolder cls = ModelUtils.copyClass(classReader, false); + linker.link(cls); for (FieldHolder field : cls.getFields().toArray(new FieldHolder[0])) { FieldReference fieldRef = new FieldReference(cls.getName(), field.getName()); @@ -994,8 +989,6 @@ public class TeaVM implements TeaVMHost, ServiceRepository { : null; if (program == null) { program = ProgramUtils.copy(classReader.getMethod(method.getDescriptor()).getProgram()); - missingItemsProcessor.processMethod(method.getReference(), program); - linker.link(method, program); clinitInsertion.apply(method, program); target.beforeInlining(program, method); program = optimizeMethodCacheMiss(method, program); diff --git a/platform/src/main/java/org/teavm/platform/plugin/AsyncLowLevelDependencyListener.java b/platform/src/main/java/org/teavm/platform/plugin/AsyncLowLevelDependencyListener.java deleted file mode 100644 index 438f56b65..000000000 --- a/platform/src/main/java/org/teavm/platform/plugin/AsyncLowLevelDependencyListener.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2018 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.platform.plugin; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import org.teavm.dependency.AbstractDependencyListener; -import org.teavm.dependency.DependencyAgent; -import org.teavm.dependency.MethodDependency; -import org.teavm.interop.Async; -import org.teavm.interop.AsyncCallback; -import org.teavm.model.AnnotationReader; -import org.teavm.model.BasicBlock; -import org.teavm.model.ClassHolder; -import org.teavm.model.ElementModifier; -import org.teavm.model.FieldHolder; -import org.teavm.model.FieldReference; -import org.teavm.model.MethodHolder; -import org.teavm.model.MethodReader; -import org.teavm.model.MethodReference; -import org.teavm.model.Program; -import org.teavm.model.ValueType; -import org.teavm.model.Variable; -import org.teavm.model.instructions.ExitInstruction; -import org.teavm.model.instructions.GetFieldInstruction; -import org.teavm.model.instructions.InvocationType; -import org.teavm.model.instructions.InvokeInstruction; -import org.teavm.model.instructions.PutFieldInstruction; -import org.teavm.runtime.Fiber; - -public class AsyncLowLevelDependencyListener extends AbstractDependencyListener { - private Set generatedClassNames = new HashSet<>(); - - @Override - public void methodReached(DependencyAgent agent, MethodDependency method) { - if (method.getMethod() != null && method.getMethod().getAnnotations().get(Async.class.getName()) != null) { - ClassHolder cls = generateCall(method.getMethod()); - if (cls != null) { - agent.submitClass(cls); - } - } - } - - private ClassHolder generateCall(MethodReader method) { - ClassHolder cls = generateClassDecl(method); - if (cls == null) { - return null; - } - cls.addMethod(generateConstructor(method, cls.getName())); - cls.addMethod(generateRun(method, cls.getName())); - return cls; - } - - private ClassHolder generateClassDecl(MethodReader method) { - AnnotationReader annot = method.getAnnotations().get(AsyncCaller.class.getName()); - String className = annot.getValue("value").getString(); - if (!generatedClassNames.add(className)) { - return null; - } - ClassHolder cls = new ClassHolder(className); - - cls.getInterfaces().add(Fiber.class.getName() + "$AsyncCall"); - - List types = new ArrayList<>(); - if (!method.hasModifier(ElementModifier.STATIC)) { - types.add(ValueType.object(method.getOwnerName())); - FieldHolder field = new FieldHolder("instance"); - field.setType(ValueType.object(method.getOwnerName())); - cls.addField(field); - } - ValueType[] parameterTypes = method.getParameterTypes(); - for (int i = 0; i < parameterTypes.length; ++i) { - types.add(parameterTypes[i]); - FieldHolder field = new FieldHolder("param" + i); - field.setType(parameterTypes[i]); - cls.addField(field); - } - types.add(ValueType.VOID); - MethodHolder constructor = new MethodHolder("", types.toArray(new ValueType[0])); - cls.addMethod(constructor); - return cls; - } - - private MethodHolder generateConstructor(MethodReader method, String className) { - List types = new ArrayList<>(); - if (!method.hasModifier(ElementModifier.STATIC)) { - types.add(ValueType.object(method.getOwnerName())); - } - Collections.addAll(types, method.getParameterTypes()); - types.add(ValueType.VOID); - MethodHolder constructor = new MethodHolder("", types.toArray(new ValueType[0])); - - Program program = new Program(); - constructor.setProgram(program); - BasicBlock block = program.createBasicBlock(); - Variable instance = program.createVariable(); - - if (!method.hasModifier(ElementModifier.STATIC)) { - PutFieldInstruction putField = new PutFieldInstruction(); - putField.setValue(program.createVariable()); - putField.setField(new FieldReference(className, "instance")); - putField.setFieldType(ValueType.object(method.getOwnerName())); - putField.setInstance(instance); - block.add(putField); - } - - for (int i = 0; i < method.parameterCount(); ++i) { - PutFieldInstruction putField = new PutFieldInstruction(); - putField.setValue(program.createVariable()); - putField.setField(new FieldReference(className, "param" + i)); - putField.setFieldType(method.parameterType(i)); - putField.setInstance(instance); - block.add(putField); - } - - block.add(new ExitInstruction()); - return constructor; - } - - private MethodHolder generateRun(MethodReader method, String className) { - MethodHolder runMethod = new MethodHolder("run", ValueType.parse(AsyncCallback.class), ValueType.VOID); - Program program = new Program(); - runMethod.setProgram(program); - BasicBlock block = program.createBasicBlock(); - Variable instance = program.createVariable(); - Variable callback = program.createVariable(); - - InvokeInstruction call = new InvokeInstruction(); - call.setType(InvocationType.SPECIAL); - List types = new ArrayList<>(); - ValueType[] parameterTypes = method.getParameterTypes(); - List arguments = new ArrayList<>(call.getArguments()); - - if (!method.hasModifier(ElementModifier.STATIC)) { - GetFieldInstruction getField = new GetFieldInstruction(); - getField.setReceiver(program.createVariable()); - getField.setInstance(instance); - getField.setField(new FieldReference(className, "instance")); - getField.setFieldType(ValueType.object(method.getOwnerName())); - block.add(getField); - call.setInstance(getField.getReceiver()); - } - for (int i = 0; i < parameterTypes.length; ++i) { - GetFieldInstruction getField = new GetFieldInstruction(); - getField.setReceiver(program.createVariable()); - getField.setInstance(instance); - getField.setField(new FieldReference(className, "param" + i)); - getField.setFieldType(parameterTypes[i]); - block.add(getField); - arguments.add(getField.getReceiver()); - types.add(parameterTypes[i]); - } - - types.add(ValueType.parse(AsyncCallback.class)); - arguments.add(callback); - - types.add(ValueType.VOID); - call.setMethod(new MethodReference(method.getOwnerName(), method.getName(), types.toArray(new ValueType[0]))); - call.setArguments(arguments.toArray(new Variable[0])); - block.add(call); - - block.add(new ExitInstruction()); - - return runMethod; - } -} diff --git a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java index 4d2f6aacd..6b212f303 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java +++ b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java @@ -16,6 +16,7 @@ package org.teavm.platform.plugin; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.teavm.backend.javascript.spi.GeneratedBy; import org.teavm.interop.Async; @@ -28,8 +29,11 @@ import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ElementModifier; +import org.teavm.model.FieldHolder; +import org.teavm.model.FieldReference; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.PrimitiveType; import org.teavm.model.Program; @@ -38,9 +42,11 @@ import org.teavm.model.Variable; import org.teavm.model.instructions.CastInstruction; import org.teavm.model.instructions.ConstructInstruction; import org.teavm.model.instructions.ExitInstruction; +import org.teavm.model.instructions.GetFieldInstruction; import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.JumpInstruction; +import org.teavm.model.instructions.PutFieldInstruction; import org.teavm.runtime.Fiber; public class AsyncMethodProcessor implements ClassHolderTransformer { @@ -75,7 +81,7 @@ public class AsyncMethodProcessor implements ClassHolderTransformer { } if (lowLevel) { - generateLowLevelCall(method, suffix++); + generateLowLevelCall(method, suffix++, context); } else { generateCallerMethod(cls, method); } @@ -83,11 +89,9 @@ public class AsyncMethodProcessor implements ClassHolderTransformer { } } - private void generateLowLevelCall(MethodHolder method, int suffix) { + private void generateLowLevelCall(MethodHolder method, int suffix, ClassHolderTransformerContext context) { String className = method.getOwnerName() + "$" + method.getName() + "$" + suffix; - AnnotationHolder classNameAnnot = new AnnotationHolder(AsyncCaller.class.getName()); - classNameAnnot.getValues().put("value", new AnnotationValue(className)); - method.getAnnotations().add(classNameAnnot); + context.submit(generateCall(method, className)); method.getModifiers().remove(ElementModifier.NATIVE); @@ -295,4 +299,122 @@ public class AsyncMethodProcessor implements ClassHolderTransformer { cast.setTargetType(ValueType.object(call.getMethod().getClassName())); return call.getReceiver(); } + + private ClassHolder generateCall(MethodReader method, String className) { + ClassHolder cls = generateClassDecl(method, className); + if (cls == null) { + return null; + } + cls.addMethod(generateConstructor(method, cls.getName())); + cls.addMethod(generateRun(method, cls.getName())); + return cls; + } + + private ClassHolder generateClassDecl(MethodReader method, String className) { + ClassHolder cls = new ClassHolder(className); + + cls.getInterfaces().add(Fiber.class.getName() + "$AsyncCall"); + + List types = new ArrayList<>(); + if (!method.hasModifier(ElementModifier.STATIC)) { + types.add(ValueType.object(method.getOwnerName())); + FieldHolder field = new FieldHolder("instance"); + field.setType(ValueType.object(method.getOwnerName())); + cls.addField(field); + } + ValueType[] parameterTypes = method.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; ++i) { + types.add(parameterTypes[i]); + FieldHolder field = new FieldHolder("param" + i); + field.setType(parameterTypes[i]); + cls.addField(field); + } + types.add(ValueType.VOID); + MethodHolder constructor = new MethodHolder("", types.toArray(new ValueType[0])); + cls.addMethod(constructor); + return cls; + } + + private MethodHolder generateConstructor(MethodReader method, String className) { + List types = new ArrayList<>(); + if (!method.hasModifier(ElementModifier.STATIC)) { + types.add(ValueType.object(method.getOwnerName())); + } + Collections.addAll(types, method.getParameterTypes()); + types.add(ValueType.VOID); + MethodHolder constructor = new MethodHolder("", types.toArray(new ValueType[0])); + + Program program = new Program(); + constructor.setProgram(program); + BasicBlock block = program.createBasicBlock(); + Variable instance = program.createVariable(); + + if (!method.hasModifier(ElementModifier.STATIC)) { + PutFieldInstruction putField = new PutFieldInstruction(); + putField.setValue(program.createVariable()); + putField.setField(new FieldReference(className, "instance")); + putField.setFieldType(ValueType.object(method.getOwnerName())); + putField.setInstance(instance); + block.add(putField); + } + + for (int i = 0; i < method.parameterCount(); ++i) { + PutFieldInstruction putField = new PutFieldInstruction(); + putField.setValue(program.createVariable()); + putField.setField(new FieldReference(className, "param" + i)); + putField.setFieldType(method.parameterType(i)); + putField.setInstance(instance); + block.add(putField); + } + + block.add(new ExitInstruction()); + return constructor; + } + + private MethodHolder generateRun(MethodReader method, String className) { + MethodHolder runMethod = new MethodHolder("run", ValueType.parse(AsyncCallback.class), ValueType.VOID); + Program program = new Program(); + runMethod.setProgram(program); + BasicBlock block = program.createBasicBlock(); + Variable instance = program.createVariable(); + Variable callback = program.createVariable(); + + InvokeInstruction call = new InvokeInstruction(); + call.setType(InvocationType.SPECIAL); + List types = new ArrayList<>(); + ValueType[] parameterTypes = method.getParameterTypes(); + List arguments = new ArrayList<>(call.getArguments()); + + if (!method.hasModifier(ElementModifier.STATIC)) { + GetFieldInstruction getField = new GetFieldInstruction(); + getField.setReceiver(program.createVariable()); + getField.setInstance(instance); + getField.setField(new FieldReference(className, "instance")); + getField.setFieldType(ValueType.object(method.getOwnerName())); + block.add(getField); + call.setInstance(getField.getReceiver()); + } + for (int i = 0; i < parameterTypes.length; ++i) { + GetFieldInstruction getField = new GetFieldInstruction(); + getField.setReceiver(program.createVariable()); + getField.setInstance(instance); + getField.setField(new FieldReference(className, "param" + i)); + getField.setFieldType(parameterTypes[i]); + block.add(getField); + arguments.add(getField.getReceiver()); + types.add(parameterTypes[i]); + } + + types.add(ValueType.parse(AsyncCallback.class)); + arguments.add(callback); + + types.add(ValueType.VOID); + call.setMethod(new MethodReference(method.getOwnerName(), method.getName(), types.toArray(new ValueType[0]))); + call.setArguments(arguments.toArray(new Variable[0])); + block.add(call); + + block.add(new ExitInstruction()); + + return runMethod; + } } diff --git a/platform/src/main/java/org/teavm/platform/plugin/PlatformPlugin.java b/platform/src/main/java/org/teavm/platform/plugin/PlatformPlugin.java index 82094aea8..788f0f32b 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/PlatformPlugin.java +++ b/platform/src/main/java/org/teavm/platform/plugin/PlatformPlugin.java @@ -125,10 +125,6 @@ public class PlatformPlugin implements TeaVMPlugin, MetadataRegistration { host.add(new EnumDependencySupport()); host.add(new PlatformDependencyListener()); - if (host.getExtension(TeaVMJavaScriptHost.class) == null) { - host.add(new AsyncLowLevelDependencyListener()); - } - TeaVMPluginUtil.handleNatives(host, Platform.class); TeaVMPluginUtil.handleNatives(host, PlatformQueue.class);