Dependency checker complete

This commit is contained in:
Alexey Andreev 2013-11-17 15:43:03 +04:00
parent 7e7cdc5b78
commit 59358dcd8d
7 changed files with 171 additions and 83 deletions

View File

@ -51,6 +51,7 @@ public class TObject {
public final void wait0(long timeout, int nanos) throws TInterruptedException { public final void wait0(long timeout, int nanos) throws TInterruptedException {
} }
@SuppressWarnings("unused")
@Rename("wait") @Rename("wait")
public final void wait0() throws TInterruptedException { public final void wait0() throws TInterruptedException {
} }

View File

@ -2,10 +2,15 @@ package org.teavm.classlibgen;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; 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.apache.commons.io.IOUtils;
import org.teavm.codegen.DefaultAliasProvider; import org.teavm.codegen.DefaultAliasProvider;
import org.teavm.codegen.DefaultNamingStrategy; import org.teavm.codegen.DefaultNamingStrategy;
import org.teavm.codegen.SourceWriter; import org.teavm.codegen.SourceWriter;
import org.teavm.dependency.DependencyChecker;
import org.teavm.javascript.Decompiler; import org.teavm.javascript.Decompiler;
import org.teavm.javascript.Renderer; import org.teavm.javascript.Renderer;
import org.teavm.javascript.ast.ClassNode; import org.teavm.javascript.ast.ClassNode;
@ -23,6 +28,9 @@ public class ClasslibTestGenerator {
private static DefaultNamingStrategy naming; private static DefaultNamingStrategy naming;
private static SourceWriter writer; private static SourceWriter writer;
private static Renderer renderer; private static Renderer renderer;
private static List<MethodReference> testMethods = new ArrayList<>();
private static Map<String, List<MethodReference>> groupedMethods = new HashMap<>();
private static String[] testClasses = { "java.lang.ObjectTests" };
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
classSource = new ClasspathClassHolderSource(); classSource = new ClasspathClassHolderSource();
@ -31,23 +39,28 @@ public class ClasslibTestGenerator {
naming = new DefaultNamingStrategy(aliasProvider, classSource); naming = new DefaultNamingStrategy(aliasProvider, classSource);
writer = new SourceWriter(naming); writer = new SourceWriter(naming);
renderer = new Renderer(writer, classSource); renderer = new Renderer(writer, classSource);
decompileClass("java.lang.Object"); DependencyChecker dependencyChecker = new DependencyChecker(classSource);
decompileClass("java.lang.ObjectTests"); for (String testClass : testClasses) {
decompileClass("java.lang.Class"); findTests(classSource.getClassHolder(testClass));
decompileClass("java.lang.annotation.Annotation"); }
decompileClass("org.junit.Assert"); for (MethodReference methodRef : testMethods) {
decompileClass("org.junit.Test"); dependencyChecker.addEntryPoint(methodRef);
}
dependencyChecker.checkDependencies();
for (String className : dependencyChecker.getAchievableClasses()) {
decompileClass(className);
}
renderHead(); renderHead();
ClassLoader classLoader = ClasslibTestGenerator.class.getClassLoader(); ClassLoader classLoader = ClasslibTestGenerator.class.getClassLoader();
try (InputStream input = classLoader.getResourceAsStream( try (InputStream input = classLoader.getResourceAsStream("org/teavm/classlib/junit-support.js")) {
"org/teavm/classlib/junit-support.js")) {
System.out.println(IOUtils.toString(input)); System.out.println(IOUtils.toString(input));
} }
try (InputStream input = classLoader.getResourceAsStream( try (InputStream input = classLoader.getResourceAsStream("org/teavm/javascript/runtime.js")) {
"org/teavm/javascript/runtime.js")) {
System.out.println(IOUtils.toString(input)); System.out.println(IOUtils.toString(input));
} }
renderClassTest(classSource.getClassHolder("java.lang.ObjectTests")); for (String testClass : testClasses) {
renderClassTest(classSource.getClassHolder(testClass));
}
System.out.println(writer); System.out.println(writer);
renderFoot(); renderFoot();
} }
@ -63,8 +76,7 @@ public class ClasslibTestGenerator {
System.out.println("<html>"); System.out.println("<html>");
System.out.println(" <head>"); System.out.println(" <head>");
System.out.println(" <title>TeaVM JUnit tests</title>"); System.out.println(" <title>TeaVM JUnit tests</title>");
System.out.println(" <meta http-equiv=\"Content-Type\" " + System.out.println(" <meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\"/>");
"content=\"text/html;charset=UTF-8\"/>");
System.out.println(" <title>TeaVM JUnit tests</title>"); System.out.println(" <title>TeaVM JUnit tests</title>");
System.out.println(" </head>"); System.out.println(" </head>");
System.out.println(" <body>"); System.out.println(" <body>");
@ -78,16 +90,29 @@ public class ClasslibTestGenerator {
} }
private static void renderClassTest(ClassHolder cls) { private static void renderClassTest(ClassHolder cls) {
List<MethodReference> methods = groupedMethods.get(cls.getName());
writer.append("testClass(\"" + cls.getName() + "\", function() {").newLine().indent(); writer.append("testClass(\"" + cls.getName() + "\", function() {").newLine().indent();
MethodReference cons = new MethodReference(cls.getName(), MethodReference cons = new MethodReference(cls.getName(), new MethodDescriptor("<init>", ValueType.VOID));
new MethodDescriptor("<init>", ValueType.VOID)); for (MethodReference method : methods) {
for (MethodHolder method : cls.getMethods()) { writer.append("runTestCase(").appendClass(cls.getName()).append(".").appendMethod(cons)
if (method.getAnnotations().get("org.junit.Test") != null) { .append("(), \"" + method.getDescriptor().getName() + "\", \"").appendMethod(method)
MethodReference ref = new MethodReference(cls.getName(), method.getDescriptor()); .append("\");").newLine();
writer.append("runTestCase(").appendClass(cls.getName()).append(".").appendMethod(cons)
.append("(), \"" + method.getName() + "\", \"").appendMethod(ref).append("\");").newLine();
}
} }
writer.outdent().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<MethodReference> group = groupedMethods.get(cls.getName());
if (group == null) {
group = new ArrayList<>();
groupedMethods.put(cls.getName(), group);
}
group.add(ref);
}
}
}
} }

View File

@ -38,7 +38,7 @@ public class DependencyChecker {
private volatile RuntimeException exceptionOccured; private volatile RuntimeException exceptionOccured;
public DependencyChecker(ClassHolderSource classSource) { public DependencyChecker(ClassHolderSource classSource) {
this(classSource, 1); this(classSource, Runtime.getRuntime().availableProcessors());
} }
public DependencyChecker(ClassHolderSource classSource, int numThreads) { public DependencyChecker(ClassHolderSource classSource, int numThreads) {
@ -103,7 +103,7 @@ public class DependencyChecker {
exceptionOccured = null; exceptionOccured = null;
while (true) { while (true) {
try { try {
if (executor.awaitTermination(1, TimeUnit.SECONDS)) { if (executor.getActiveCount() == 0 || executor.awaitTermination(1, TimeUnit.SECONDS)) {
break; break;
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -125,7 +125,7 @@ public class DependencyChecker {
} }
private void initClass(String className) { private void initClass(String className) {
MethodDescriptor clinitDesc = new MethodDescriptor("<clinit>"); MethodDescriptor clinitDesc = new MethodDescriptor("<clinit>", ValueType.VOID);
while (className != null) { while (className != null) {
if (initializedClasses.putIfAbsent(className, clinitDesc) != null) { if (initializedClasses.putIfAbsent(className, clinitDesc) != null) {
break; break;
@ -166,6 +166,7 @@ public class DependencyChecker {
MethodGraph graph = new MethodGraph(parameterNodes, paramCount, resultNode, this); MethodGraph graph = new MethodGraph(parameterNodes, paramCount, resultNode, this);
DependencyGraphBuilder graphBuilder = new DependencyGraphBuilder(this); DependencyGraphBuilder graphBuilder = new DependencyGraphBuilder(this);
graphBuilder.buildGraph(method, graph); graphBuilder.buildGraph(method, graph);
achieveClass(methodRef.getClassName());
return graph; return graph;
} }

View File

@ -15,7 +15,7 @@
*/ */
package org.teavm.dependency; package org.teavm.dependency;
import java.util.Set; import java.util.List;
import org.teavm.model.*; import org.teavm.model.*;
import org.teavm.model.instructions.*; import org.teavm.model.instructions.*;
@ -25,15 +25,12 @@ import org.teavm.model.instructions.*;
*/ */
class DependencyGraphBuilder { class DependencyGraphBuilder {
private DependencyChecker dependencyChecker; private DependencyChecker dependencyChecker;
private ClassHolderSource classSource;
private DependencyNode[] nodes; private DependencyNode[] nodes;
private DependencyNode resultNode; private DependencyNode resultNode;
private Program program; private Program program;
private ValueType resultType;
public DependencyGraphBuilder(DependencyChecker dependencyChecker) { public DependencyGraphBuilder(DependencyChecker dependencyChecker) {
this.dependencyChecker = dependencyChecker; this.dependencyChecker = dependencyChecker;
this.classSource = dependencyChecker.getClassSource();
} }
public void buildGraph(MethodHolder method, MethodGraph graph) { public void buildGraph(MethodHolder method, MethodGraph graph) {
@ -41,7 +38,6 @@ class DependencyGraphBuilder {
return; return;
} }
program = method.getProgram(); program = method.getProgram();
resultType = method.getResultType();
resultNode = graph.getResultNode(); resultNode = graph.getResultNode();
nodes = graph.getVariableNodes(); nodes = graph.getVariableNodes();
for (int i = 0; i < program.basicBlockCount(); ++i) { for (int i = 0; i < program.basicBlockCount(); ++i) {
@ -57,38 +53,27 @@ class DependencyGraphBuilder {
} }
} }
private static boolean hasBody(MethodHolder method) {
Set<ElementModifier> modifiers = method.getModifiers();
return !modifiers.contains(ElementModifier.ABSTRACT) &&
!modifiers.contains(ElementModifier.NATIVE);
}
private static class VirtualCallPropagationListener implements DependencyConsumer { private static class VirtualCallPropagationListener implements DependencyConsumer {
private final DependencyNode node; private final DependencyNode node;
private final MethodDescriptor methodDesc; private final MethodDescriptor methodDesc;
private final DependencyChecker checker; private final DependencyChecker checker;
private final ValueType[] paramTypes;
private final DependencyNode[] parameters; private final DependencyNode[] parameters;
private final ValueType resultType;
private final DependencyNode result; private final DependencyNode result;
public VirtualCallPropagationListener(DependencyNode node, MethodDescriptor methodDesc, public VirtualCallPropagationListener(DependencyNode node, MethodDescriptor methodDesc,
DependencyChecker checker, ValueType[] paramTypes, DependencyNode[] parameters, DependencyChecker checker, DependencyNode[] parameters, DependencyNode result) {
ValueType resultType, DependencyNode result) {
this.node = node; this.node = node;
this.methodDesc = methodDesc; this.methodDesc = methodDesc;
this.checker = checker; this.checker = checker;
this.paramTypes = paramTypes;
this.parameters = parameters; this.parameters = parameters;
this.resultType = resultType;
this.result = result; this.result = result;
} }
@Override @Override
public void consume(String className) { public void consume(String className) {
if (DependencyChecker.shouldLog) { if (DependencyChecker.shouldLog) {
System.out.println("Virtual call of " + methodDesc + " detected on " + System.out.println("Virtual call of " + methodDesc + " detected on " + node.getTag() + ". " +
node.getTag() + ". Target class is " + className); "Target class is " + className);
} }
MethodReference methodRef = new MethodReference(className, methodDesc); MethodReference methodRef = new MethodReference(className, methodDesc);
MethodHolder method = findMethod(methodRef, checker.getClassSource()); MethodHolder method = findMethod(methodRef, checker.getClassSource());
@ -122,43 +107,6 @@ class DependencyGraphBuilder {
return null; 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() { private InstructionVisitor visitor = new InstructionVisitor() {
@Override @Override
public void visit(IsInstanceInstruction insn) { public void visit(IsInstanceInstruction insn) {
@ -166,18 +114,74 @@ class DependencyGraphBuilder {
@Override @Override
public void visit(InvokeInstruction insn) { 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<Variable> 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<Variable> 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 @Override
public void visit(PutElementInstruction insn) { public void visit(PutElementInstruction insn) {
DependencyNode valueNode = nodes[insn.getValue().getIndex()];
DependencyNode arrayNode = nodes[insn.getArray().getIndex()];
valueNode.connect(arrayNode.getArrayItemNode());
} }
@Override @Override
public void visit(GetElementInstruction insn) { public void visit(GetElementInstruction insn) {
DependencyNode arrayNode = nodes[insn.getArray().getIndex()];
DependencyNode receiverNode = nodes[insn.getReceiver().getIndex()];
arrayNode.getArrayItemNode().connect(receiverNode);
} }
@Override @Override
public void visit(CloneArrayInstruction insn) { 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 @Override
@ -186,22 +190,38 @@ class DependencyGraphBuilder {
@Override @Override
public void visit(PutFieldInstruction insn) { 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 @Override
public void visit(GetFieldInstruction insn) { 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 @Override
public void visit(ConstructMultiArrayInstruction insn) { 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 @Override
public void visit(ConstructInstruction insn) { public void visit(ConstructInstruction insn) {
nodes[insn.getReceiver().getIndex()].propagate(insn.getType());
} }
@Override @Override
public void visit(ConstructArrayInstruction insn) { public void visit(ConstructArrayInstruction insn) {
nodes[insn.getReceiver().getIndex()].propagate("[" + insn.getItemType());
} }
@Override @Override
@ -210,6 +230,9 @@ class DependencyGraphBuilder {
@Override @Override
public void visit(ExitInstruction insn) { public void visit(ExitInstruction insn) {
if (insn.getValueToReturn() != null) {
nodes[insn.getValueToReturn().getIndex()].connect(resultNode);
}
} }
@Override @Override
@ -234,10 +257,16 @@ class DependencyGraphBuilder {
@Override @Override
public void visit(CastInstruction insn) { public void visit(CastInstruction insn) {
DependencyNode valueNode = nodes[insn.getValue().getIndex()];
DependencyNode receiverNode = nodes[insn.getReceiver().getIndex()];
valueNode.connect(receiverNode);
} }
@Override @Override
public void visit(AssignInstruction insn) { public void visit(AssignInstruction insn) {
DependencyNode valueNode = nodes[insn.getAssignee().getIndex()];
DependencyNode receiverNode = nodes[insn.getReceiver().getIndex()];
valueNode.connect(receiverNode);
} }
@Override @Override
@ -250,6 +279,7 @@ class DependencyGraphBuilder {
@Override @Override
public void visit(StringConstantInstruction insn) { public void visit(StringConstantInstruction insn) {
nodes[insn.getReceiver().getIndex()].propagate("java.lang.String");
} }
@Override @Override
@ -274,6 +304,14 @@ class DependencyGraphBuilder {
@Override @Override
public void visit(ClassConstantInstruction insn) { 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 @Override

View File

@ -57,13 +57,17 @@ public class DependencyNode {
} }
} }
public void connect(DependencyNode node) { public void connect(DependencyNode node, DependencyTypeFilter filter) {
DependencyNodeToNodeTransition transition = new DependencyNodeToNodeTransition(this, node); DependencyNodeToNodeTransition transition = new DependencyNodeToNodeTransition(this, node, filter);
if (transitions.putIfAbsent(node, transition) == null) { if (transitions.putIfAbsent(node, transition) == null) {
addConsumer(transition); addConsumer(transition);
} }
} }
public void connect(DependencyNode node) {
connect(node, null);
}
public DependencyNode getArrayItemNode() { public DependencyNode getArrayItemNode() {
DependencyNode result = arrayItemNode.get(); DependencyNode result = arrayItemNode.get();
if (result == null) { if (result == null) {
@ -87,6 +91,10 @@ public class DependencyNode {
return result; return result;
} }
public boolean hasArrayType() {
return arrayItemNode.get() != null && !arrayItemNode.get().types.isEmpty();
}
public boolean hasType(String type) { public boolean hasType(String type) {
return types.containsKey(type); return types.containsKey(type);
} }

View File

@ -7,14 +7,20 @@ package org.teavm.dependency;
class DependencyNodeToNodeTransition implements DependencyConsumer { class DependencyNodeToNodeTransition implements DependencyConsumer {
private DependencyNode source; private DependencyNode source;
private DependencyNode destination; private DependencyNode destination;
private DependencyTypeFilter filter;
public DependencyNodeToNodeTransition(DependencyNode source, DependencyNode destination) { public DependencyNodeToNodeTransition(DependencyNode source, DependencyNode destination,
DependencyTypeFilter filter) {
this.source = source; this.source = source;
this.destination = destination; this.destination = destination;
this.filter = filter;
} }
@Override @Override
public void consume(String type) { public void consume(String type) {
if (filter != null && !filter.match(type)) {
return;
}
if (!destination.hasType(type)) { if (!destination.hasType(type)) {
destination.propagate(type); destination.propagate(type);
if (type.startsWith("[")) { if (type.startsWith("[")) {

View File

@ -0,0 +1,9 @@
package org.teavm.dependency;
/**
*
* @author Alexey Andreev
*/
public interface DependencyTypeFilter {
boolean match(String type);
}