diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java index e60553703..dfd3affd6 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java @@ -51,6 +51,7 @@ public class TObject { public final void wait0(long timeout, int nanos) throws TInterruptedException { } + @SuppressWarnings("unused") @Rename("wait") public final void wait0() throws TInterruptedException { } diff --git a/teavm-classlib/src/main/java/org/teavm/classlibgen/ClasslibTestGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlibgen/ClasslibTestGenerator.java index cfa5a23eb..5faa6da8a 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlibgen/ClasslibTestGenerator.java +++ b/teavm-classlib/src/main/java/org/teavm/classlibgen/ClasslibTestGenerator.java @@ -2,10 +2,15 @@ package org.teavm.classlibgen; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.apache.commons.io.IOUtils; import org.teavm.codegen.DefaultAliasProvider; import org.teavm.codegen.DefaultNamingStrategy; import org.teavm.codegen.SourceWriter; +import org.teavm.dependency.DependencyChecker; import org.teavm.javascript.Decompiler; import org.teavm.javascript.Renderer; import org.teavm.javascript.ast.ClassNode; @@ -23,6 +28,9 @@ public class ClasslibTestGenerator { private static DefaultNamingStrategy naming; private static SourceWriter writer; private static Renderer renderer; + private static List testMethods = new ArrayList<>(); + private static Map> groupedMethods = new HashMap<>(); + private static String[] testClasses = { "java.lang.ObjectTests" }; public static void main(String[] args) throws IOException { classSource = new ClasspathClassHolderSource(); @@ -31,23 +39,28 @@ public class ClasslibTestGenerator { naming = new DefaultNamingStrategy(aliasProvider, classSource); writer = new SourceWriter(naming); renderer = new Renderer(writer, classSource); - decompileClass("java.lang.Object"); - decompileClass("java.lang.ObjectTests"); - decompileClass("java.lang.Class"); - decompileClass("java.lang.annotation.Annotation"); - decompileClass("org.junit.Assert"); - decompileClass("org.junit.Test"); + DependencyChecker dependencyChecker = new DependencyChecker(classSource); + for (String testClass : testClasses) { + findTests(classSource.getClassHolder(testClass)); + } + for (MethodReference methodRef : testMethods) { + dependencyChecker.addEntryPoint(methodRef); + } + dependencyChecker.checkDependencies(); + for (String className : dependencyChecker.getAchievableClasses()) { + decompileClass(className); + } renderHead(); ClassLoader classLoader = ClasslibTestGenerator.class.getClassLoader(); - try (InputStream input = classLoader.getResourceAsStream( - "org/teavm/classlib/junit-support.js")) { + try (InputStream input = classLoader.getResourceAsStream("org/teavm/classlib/junit-support.js")) { System.out.println(IOUtils.toString(input)); } - try (InputStream input = classLoader.getResourceAsStream( - "org/teavm/javascript/runtime.js")) { + try (InputStream input = classLoader.getResourceAsStream("org/teavm/javascript/runtime.js")) { System.out.println(IOUtils.toString(input)); } - renderClassTest(classSource.getClassHolder("java.lang.ObjectTests")); + for (String testClass : testClasses) { + renderClassTest(classSource.getClassHolder(testClass)); + } System.out.println(writer); renderFoot(); } @@ -63,8 +76,7 @@ public class ClasslibTestGenerator { System.out.println(""); System.out.println(" "); System.out.println(" TeaVM JUnit tests"); - System.out.println(" "); + System.out.println(" "); System.out.println(" TeaVM JUnit tests"); System.out.println(" "); System.out.println(" "); @@ -78,16 +90,29 @@ public class ClasslibTestGenerator { } private static void renderClassTest(ClassHolder cls) { + List methods = groupedMethods.get(cls.getName()); writer.append("testClass(\"" + cls.getName() + "\", function() {").newLine().indent(); - MethodReference cons = new MethodReference(cls.getName(), - new MethodDescriptor("", ValueType.VOID)); - for (MethodHolder method : cls.getMethods()) { - if (method.getAnnotations().get("org.junit.Test") != null) { - MethodReference ref = new MethodReference(cls.getName(), method.getDescriptor()); - writer.append("runTestCase(").appendClass(cls.getName()).append(".").appendMethod(cons) - .append("(), \"" + method.getName() + "\", \"").appendMethod(ref).append("\");").newLine(); - } + MethodReference cons = new MethodReference(cls.getName(), new MethodDescriptor("", ValueType.VOID)); + for (MethodReference method : methods) { + writer.append("runTestCase(").appendClass(cls.getName()).append(".").appendMethod(cons) + .append("(), \"" + method.getDescriptor().getName() + "\", \"").appendMethod(method) + .append("\");").newLine(); } writer.outdent().append("})").newLine(); } + + private static void findTests(ClassHolder cls) { + for (MethodHolder method : cls.getMethods()) { + if (method.getAnnotations().get("org.junit.Test") != null) { + MethodReference ref = new MethodReference(cls.getName(), method.getDescriptor()); + testMethods.add(ref); + List group = groupedMethods.get(cls.getName()); + if (group == null) { + group = new ArrayList<>(); + groupedMethods.put(cls.getName(), group); + } + group.add(ref); + } + } + } } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java index ac51ccac5..99c12487c 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java @@ -38,7 +38,7 @@ public class DependencyChecker { private volatile RuntimeException exceptionOccured; public DependencyChecker(ClassHolderSource classSource) { - this(classSource, 1); + this(classSource, Runtime.getRuntime().availableProcessors()); } public DependencyChecker(ClassHolderSource classSource, int numThreads) { @@ -103,7 +103,7 @@ public class DependencyChecker { exceptionOccured = null; while (true) { try { - if (executor.awaitTermination(1, TimeUnit.SECONDS)) { + if (executor.getActiveCount() == 0 || executor.awaitTermination(1, TimeUnit.SECONDS)) { break; } } catch (InterruptedException e) { @@ -125,7 +125,7 @@ public class DependencyChecker { } private void initClass(String className) { - MethodDescriptor clinitDesc = new MethodDescriptor(""); + MethodDescriptor clinitDesc = new MethodDescriptor("", ValueType.VOID); while (className != null) { if (initializedClasses.putIfAbsent(className, clinitDesc) != null) { break; @@ -166,6 +166,7 @@ public class DependencyChecker { MethodGraph graph = new MethodGraph(parameterNodes, paramCount, resultNode, this); DependencyGraphBuilder graphBuilder = new DependencyGraphBuilder(this); graphBuilder.buildGraph(method, graph); + achieveClass(methodRef.getClassName()); return graph; } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java index 21c4f93fa..4fa6e92fa 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java @@ -15,7 +15,7 @@ */ package org.teavm.dependency; -import java.util.Set; +import java.util.List; import org.teavm.model.*; import org.teavm.model.instructions.*; @@ -25,15 +25,12 @@ import org.teavm.model.instructions.*; */ class DependencyGraphBuilder { private DependencyChecker dependencyChecker; - private ClassHolderSource classSource; private DependencyNode[] nodes; private DependencyNode resultNode; private Program program; - private ValueType resultType; public DependencyGraphBuilder(DependencyChecker dependencyChecker) { this.dependencyChecker = dependencyChecker; - this.classSource = dependencyChecker.getClassSource(); } public void buildGraph(MethodHolder method, MethodGraph graph) { @@ -41,7 +38,6 @@ class DependencyGraphBuilder { return; } program = method.getProgram(); - resultType = method.getResultType(); resultNode = graph.getResultNode(); nodes = graph.getVariableNodes(); for (int i = 0; i < program.basicBlockCount(); ++i) { @@ -57,38 +53,27 @@ class DependencyGraphBuilder { } } - private static boolean hasBody(MethodHolder method) { - Set modifiers = method.getModifiers(); - return !modifiers.contains(ElementModifier.ABSTRACT) && - !modifiers.contains(ElementModifier.NATIVE); - } - private static class VirtualCallPropagationListener implements DependencyConsumer { private final DependencyNode node; private final MethodDescriptor methodDesc; private final DependencyChecker checker; - private final ValueType[] paramTypes; private final DependencyNode[] parameters; - private final ValueType resultType; private final DependencyNode result; public VirtualCallPropagationListener(DependencyNode node, MethodDescriptor methodDesc, - DependencyChecker checker, ValueType[] paramTypes, DependencyNode[] parameters, - ValueType resultType, DependencyNode result) { + DependencyChecker checker, DependencyNode[] parameters, DependencyNode result) { this.node = node; this.methodDesc = methodDesc; this.checker = checker; - this.paramTypes = paramTypes; this.parameters = parameters; - this.resultType = resultType; this.result = result; } @Override public void consume(String className) { if (DependencyChecker.shouldLog) { - System.out.println("Virtual call of " + methodDesc + " detected on " + - node.getTag() + ". Target class is " + className); + System.out.println("Virtual call of " + methodDesc + " detected on " + node.getTag() + ". " + + "Target class is " + className); } MethodReference methodRef = new MethodReference(className, methodDesc); MethodHolder method = findMethod(methodRef, checker.getClassSource()); @@ -122,43 +107,6 @@ class DependencyGraphBuilder { return null; } - private static MethodHolder requireMethod(MethodReference methodRef, ClassHolderSource classSource) { - MethodHolder method = findMethod(methodRef, classSource); - if (method == null) { - throw new IllegalStateException("Method not found: " + methodRef); - } - return method; - } - - private static FieldHolder findField(FieldReference fieldRef, ClassHolderSource classSource) { - String className = fieldRef.getClassName(); - while (className != null) { - ClassHolder cls = classSource.getClassHolder(className); - if (cls == null) { - break; - } - FieldHolder field = cls.getField(fieldRef.getFieldName()); - if (field != null) { - return field; - } - className = cls.getParent(); - } - return null; - } - - private static boolean isPossibleArrayPair(ValueType a, ValueType b) { - if (a instanceof ValueType.Array || b instanceof ValueType.Array) { - return true; - } - if (a == null || b == null) { - return false; - } - if (a.toString().equals("Ljava/lang/Object;") && b.toString().equals("Ljava/lang/Object;")) { - return true; - } - return false; - } - private InstructionVisitor visitor = new InstructionVisitor() { @Override public void visit(IsInstanceInstruction insn) { @@ -166,18 +114,74 @@ class DependencyGraphBuilder { @Override public void visit(InvokeInstruction insn) { + if (insn.getInstance() == null) { + invokeSpecial(insn); + } else { + switch (insn.getType()) { + case SPECIAL: + invokeSpecial(insn); + break; + case VIRTUAL: + invokeVirtual(insn); + break; + } + } + } + + private void invokeSpecial(InvokeInstruction insn) { + MethodReference method = new MethodReference(insn.getClassName(), insn.getMethod()); + MethodGraph targetGraph = dependencyChecker.attachMethodGraph(method); + DependencyNode[] targetParams = targetGraph.getVariableNodes(); + List arguments = insn.getArguments(); + for (int i = 0; i < arguments.size(); ++i) { + nodes[arguments.get(i).getIndex()].connect(targetParams[i + 1]); + } + if (insn.getInstance() != null) { + nodes[insn.getInstance().getIndex()].connect(targetParams[0]); + } + if (targetGraph.getResultNode() != null) { + targetGraph.getResultNode().connect(nodes[insn.getReceiver().getIndex()]); + } + } + + private void invokeVirtual(InvokeInstruction insn) { + List arguments = insn.getArguments(); + DependencyNode[] actualArgs = new DependencyNode[arguments.size() + 1]; + for (int i = 0; i < arguments.size(); ++i) { + actualArgs[i + 1] = nodes[arguments.get(i).getIndex()]; + } + actualArgs[0] = nodes[insn.getInstance().getIndex()]; + DependencyConsumer listener = new VirtualCallPropagationListener(nodes[insn.getInstance().getIndex()], + insn.getMethod(), dependencyChecker, actualArgs, + insn.getReceiver() != null ? nodes[insn.getReceiver().getIndex()] : null); + nodes[insn.getInstance().getIndex()].addConsumer(listener); } @Override public void visit(PutElementInstruction insn) { + DependencyNode valueNode = nodes[insn.getValue().getIndex()]; + DependencyNode arrayNode = nodes[insn.getArray().getIndex()]; + valueNode.connect(arrayNode.getArrayItemNode()); } @Override public void visit(GetElementInstruction insn) { + DependencyNode arrayNode = nodes[insn.getArray().getIndex()]; + DependencyNode receiverNode = nodes[insn.getReceiver().getIndex()]; + arrayNode.getArrayItemNode().connect(receiverNode); } @Override public void visit(CloneArrayInstruction insn) { + DependencyNode arrayNode = nodes[insn.getArray().getIndex()]; + final DependencyNode receiverNode = nodes[insn.getReceiver().getIndex()]; + arrayNode.addConsumer(new DependencyConsumer() { + @Override public void consume(String type) { + receiverNode.propagate(type); + + } + }); + arrayNode.getArrayItemNode().connect(receiverNode.getArrayItemNode()); } @Override @@ -186,22 +190,38 @@ class DependencyGraphBuilder { @Override public void visit(PutFieldInstruction insn) { + DependencyNode fieldNode = dependencyChecker.getFieldNode(new FieldReference(insn.getClassName(), + insn.getField())); + DependencyNode valueNode = nodes[insn.getValue().getIndex()]; + valueNode.connect(fieldNode); } @Override public void visit(GetFieldInstruction insn) { + DependencyNode fieldNode = dependencyChecker.getFieldNode(new FieldReference(insn.getClassName(), + insn.getField())); + DependencyNode receiverNode = nodes[insn.getReceiver().getIndex()]; + fieldNode.connect(receiverNode); } @Override public void visit(ConstructMultiArrayInstruction insn) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < insn.getDimensions().size(); ++i) { + sb.append('['); + } + sb.append(insn.getItemType()); + nodes[insn.getReceiver().getIndex()].propagate(sb.toString()); } @Override public void visit(ConstructInstruction insn) { + nodes[insn.getReceiver().getIndex()].propagate(insn.getType()); } @Override public void visit(ConstructArrayInstruction insn) { + nodes[insn.getReceiver().getIndex()].propagate("[" + insn.getItemType()); } @Override @@ -210,6 +230,9 @@ class DependencyGraphBuilder { @Override public void visit(ExitInstruction insn) { + if (insn.getValueToReturn() != null) { + nodes[insn.getValueToReturn().getIndex()].connect(resultNode); + } } @Override @@ -234,10 +257,16 @@ class DependencyGraphBuilder { @Override public void visit(CastInstruction insn) { + DependencyNode valueNode = nodes[insn.getValue().getIndex()]; + DependencyNode receiverNode = nodes[insn.getReceiver().getIndex()]; + valueNode.connect(receiverNode); } @Override public void visit(AssignInstruction insn) { + DependencyNode valueNode = nodes[insn.getAssignee().getIndex()]; + DependencyNode receiverNode = nodes[insn.getReceiver().getIndex()]; + valueNode.connect(receiverNode); } @Override @@ -250,6 +279,7 @@ class DependencyGraphBuilder { @Override public void visit(StringConstantInstruction insn) { + nodes[insn.getReceiver().getIndex()].propagate("java.lang.String"); } @Override @@ -274,6 +304,14 @@ class DependencyGraphBuilder { @Override public void visit(ClassConstantInstruction insn) { + nodes[insn.getReceiver().getIndex()].propagate("java.lang.Class"); + ValueType type = insn.getConstant(); + while (type instanceof ValueType.Array) { + type = ((ValueType.Array)type).getItemType(); + } + if (type instanceof ValueType.Object) { + dependencyChecker.achieveClass(((ValueType.Object)type).getClassName()); + } } @Override diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyNode.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyNode.java index 2d8040b3b..e131eaaa0 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyNode.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyNode.java @@ -57,13 +57,17 @@ public class DependencyNode { } } - public void connect(DependencyNode node) { - DependencyNodeToNodeTransition transition = new DependencyNodeToNodeTransition(this, node); + public void connect(DependencyNode node, DependencyTypeFilter filter) { + DependencyNodeToNodeTransition transition = new DependencyNodeToNodeTransition(this, node, filter); if (transitions.putIfAbsent(node, transition) == null) { addConsumer(transition); } } + public void connect(DependencyNode node) { + connect(node, null); + } + public DependencyNode getArrayItemNode() { DependencyNode result = arrayItemNode.get(); if (result == null) { @@ -87,6 +91,10 @@ public class DependencyNode { return result; } + public boolean hasArrayType() { + return arrayItemNode.get() != null && !arrayItemNode.get().types.isEmpty(); + } + public boolean hasType(String type) { return types.containsKey(type); } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyNodeToNodeTransition.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyNodeToNodeTransition.java index 0108e46a3..48b9379c0 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyNodeToNodeTransition.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyNodeToNodeTransition.java @@ -7,14 +7,20 @@ package org.teavm.dependency; class DependencyNodeToNodeTransition implements DependencyConsumer { private DependencyNode source; private DependencyNode destination; + private DependencyTypeFilter filter; - public DependencyNodeToNodeTransition(DependencyNode source, DependencyNode destination) { + public DependencyNodeToNodeTransition(DependencyNode source, DependencyNode destination, + DependencyTypeFilter filter) { this.source = source; this.destination = destination; + this.filter = filter; } @Override public void consume(String type) { + if (filter != null && !filter.match(type)) { + return; + } if (!destination.hasType(type)) { destination.propagate(type); if (type.startsWith("[")) { diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyTypeFilter.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyTypeFilter.java new file mode 100644 index 000000000..87f048195 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyTypeFilter.java @@ -0,0 +1,9 @@ +package org.teavm.dependency; + +/** + * + * @author Alexey Andreev + */ +public interface DependencyTypeFilter { + boolean match(String type); +}