diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index abc9bfdca..541adb8f4 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -90,6 +90,7 @@
+
diff --git a/core/src/main/java/org/teavm/dependency/DependencyInfo.java b/core/src/main/java/org/teavm/dependency/DependencyInfo.java
index 4be420aaa..a4f41dfb0 100644
--- a/core/src/main/java/org/teavm/dependency/DependencyInfo.java
+++ b/core/src/main/java/org/teavm/dependency/DependencyInfo.java
@@ -21,10 +21,6 @@ import org.teavm.model.ClassReaderSource;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodReference;
-/**
- *
- * @author Alexey Andreev
- */
public interface DependencyInfo {
ClassReaderSource getClassSource();
diff --git a/core/src/main/java/org/teavm/dependency/DependencyNode.java b/core/src/main/java/org/teavm/dependency/DependencyNode.java
index 0adb70814..995df8137 100644
--- a/core/src/main/java/org/teavm/dependency/DependencyNode.java
+++ b/core/src/main/java/org/teavm/dependency/DependencyNode.java
@@ -191,11 +191,11 @@ public class DependencyNode implements ValueDependencyInfo {
if (DependencyChecker.shouldLog) {
arrayItemNode.tag = tag + "[";
}
- arrayItemNode.addConsumer(this::propagate);
}
return arrayItemNode;
}
+ @Override
public DependencyNode getClassValueNode() {
if (classValueNode == null) {
classValueNode = new DependencyNode(dependencyChecker, degree);
@@ -203,7 +203,6 @@ public class DependencyNode implements ValueDependencyInfo {
if (DependencyChecker.shouldLog) {
classValueNode.tag = tag + "@";
}
- classValueNode.addConsumer(this::propagate);
}
return classValueNode;
}
diff --git a/core/src/main/java/org/teavm/dependency/MethodDependency.java b/core/src/main/java/org/teavm/dependency/MethodDependency.java
index 003039a7f..3a4e89195 100644
--- a/core/src/main/java/org/teavm/dependency/MethodDependency.java
+++ b/core/src/main/java/org/teavm/dependency/MethodDependency.java
@@ -20,10 +20,6 @@ import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
-/**
- *
- * @author Alexey Andreev
- */
public class MethodDependency implements MethodDependencyInfo {
private DependencyChecker dependencyChecker;
DependencyNode[] variableNodes;
diff --git a/core/src/main/java/org/teavm/dependency/MethodDependencyInfo.java b/core/src/main/java/org/teavm/dependency/MethodDependencyInfo.java
index df16f04f5..71e2398b3 100644
--- a/core/src/main/java/org/teavm/dependency/MethodDependencyInfo.java
+++ b/core/src/main/java/org/teavm/dependency/MethodDependencyInfo.java
@@ -17,10 +17,6 @@ package org.teavm.dependency;
import org.teavm.model.MethodReference;
-/**
- *
- * @author Alexey Andreev
- */
public interface MethodDependencyInfo {
ValueDependencyInfo[] getVariables();
diff --git a/core/src/main/java/org/teavm/dependency/ValueDependencyInfo.java b/core/src/main/java/org/teavm/dependency/ValueDependencyInfo.java
index fb5731c9c..6dfdab0a8 100644
--- a/core/src/main/java/org/teavm/dependency/ValueDependencyInfo.java
+++ b/core/src/main/java/org/teavm/dependency/ValueDependencyInfo.java
@@ -15,10 +15,6 @@
*/
package org.teavm.dependency;
-/**
- *
- * @author Alexey Andreev
- */
public interface ValueDependencyInfo {
String[] getTypes();
diff --git a/core/src/main/java/org/teavm/model/analysis/ClassInference.java b/core/src/main/java/org/teavm/model/analysis/ClassInference.java
new file mode 100644
index 000000000..794ddf8e6
--- /dev/null
+++ b/core/src/main/java/org/teavm/model/analysis/ClassInference.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright 2017 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.analysis;
+
+import com.carrotsearch.hppc.IntObjectMap;
+import com.carrotsearch.hppc.IntObjectOpenHashMap;
+import com.carrotsearch.hppc.IntOpenHashSet;
+import com.carrotsearch.hppc.IntSet;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import org.teavm.common.Graph;
+import org.teavm.common.GraphBuilder;
+import org.teavm.dependency.DependencyInfo;
+import org.teavm.dependency.FieldDependencyInfo;
+import org.teavm.dependency.MethodDependencyInfo;
+import org.teavm.dependency.ValueDependencyInfo;
+import org.teavm.model.BasicBlock;
+import org.teavm.model.ClassReaderSource;
+import org.teavm.model.Incoming;
+import org.teavm.model.Instruction;
+import org.teavm.model.MethodReader;
+import org.teavm.model.MethodReference;
+import org.teavm.model.Phi;
+import org.teavm.model.Program;
+import org.teavm.model.TryCatchBlock;
+import org.teavm.model.ValueType;
+import org.teavm.model.Variable;
+import org.teavm.model.instructions.AbstractInstructionVisitor;
+import org.teavm.model.instructions.AssignInstruction;
+import org.teavm.model.instructions.CastInstruction;
+import org.teavm.model.instructions.ClassConstantInstruction;
+import org.teavm.model.instructions.CloneArrayInstruction;
+import org.teavm.model.instructions.ConstructArrayInstruction;
+import org.teavm.model.instructions.ConstructInstruction;
+import org.teavm.model.instructions.ConstructMultiArrayInstruction;
+import org.teavm.model.instructions.GetElementInstruction;
+import org.teavm.model.instructions.GetFieldInstruction;
+import org.teavm.model.instructions.InvocationType;
+import org.teavm.model.instructions.InvokeInstruction;
+import org.teavm.model.instructions.PutElementInstruction;
+import org.teavm.model.instructions.PutFieldInstruction;
+import org.teavm.model.instructions.RaiseInstruction;
+import org.teavm.model.instructions.StringConstantInstruction;
+import org.teavm.model.instructions.UnwrapArrayInstruction;
+
+public class ClassInference {
+ private DependencyInfo dependencyInfo;
+ private Graph assignmentGraph;
+ private Graph cloneGraph;
+ private Graph arrayGraph;
+ private Graph itemGraph;
+ private List> casts;
+ private IntObjectMap exceptionMap;
+ private VirtualCallSite[][] virtualCallSites;
+ private List initialTasks;
+ private List>> types;
+ private List> finalTypes;
+
+ public ClassInference(DependencyInfo dependencyInfo) {
+ this.dependencyInfo = dependencyInfo;
+ }
+
+ public void infer(Program program, MethodReference methodReference) {
+ buildGraphs(program);
+
+ MethodDependencyInfo thisMethodDep = dependencyInfo.getMethod(methodReference);
+ for (String thisType : thisMethodDep.getVariable(0).getTypes()) {
+ initialTasks.add(new Task(0, 0, thisType));
+ }
+
+ types = new ArrayList<>(program.variableCount());
+ for (int i = 0; i < program.variableCount(); ++i) {
+ List> variableTypes = new ArrayList<>();
+ types.add(variableTypes);
+ for (int j = 0; j < 3; ++j) {
+ variableTypes.add(new LinkedHashSet<>());
+ }
+ }
+
+ propagate(program);
+ assignmentGraph = null;
+ cloneGraph = null;
+ arrayGraph = null;
+ itemGraph = null;
+ casts = null;
+ exceptionMap = null;
+ virtualCallSites = null;
+
+ finalTypes = new ArrayList<>(program.variableCount());
+ for (int i = 0; i < program.variableCount(); ++i) {
+ finalTypes.add(types.get(i).get(0));
+ }
+
+ types = null;
+ }
+
+ public String[] classesOf(int variableIndex) {
+ return finalTypes.get(variableIndex).toArray(new String[0]);
+ }
+
+ private void buildGraphs(Program program) {
+ GraphBuildingVisitor visitor = new GraphBuildingVisitor(program.variableCount(), dependencyInfo);
+ for (BasicBlock block : program.getBasicBlocks()) {
+ visitor.currentBlock = block;
+ for (Phi phi : block.getPhis()) {
+ visitor.visit(phi);
+ }
+ for (Instruction insn : block) {
+ insn.acceptVisitor(visitor);
+ }
+ }
+
+ assignmentGraph = visitor.assignmentGraphBuilder.build();
+ cloneGraph = visitor.cloneGraphBuilder.build();
+ arrayGraph = visitor.arrayGraphBuilder.build();
+ itemGraph = visitor.itemGraphBuilder.build();
+ casts = visitor.casts;
+ exceptionMap = visitor.exceptionMap;
+ initialTasks = visitor.tasks;
+
+ virtualCallSites = new VirtualCallSite[program.variableCount()][];
+ for (int i = 0; i < virtualCallSites.length; ++i) {
+ List buildCallSites = visitor.virtualCallSites.get(i);
+ if (buildCallSites != null) {
+ virtualCallSites[i] = buildCallSites.toArray(new VirtualCallSite[0]);
+ }
+ }
+ }
+
+ private void propagate(Program program) {
+ ClassReaderSource classSource = dependencyInfo.getClassSource();
+
+ Queue queue = new ArrayDeque<>();
+ queue.addAll(initialTasks);
+ initialTasks = null;
+
+ while (!queue.isEmpty()) {
+ Task task = queue.remove();
+
+ if (task.degree < 0) {
+ BasicBlock block = program.basicBlockAt(task.variable);
+ for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
+ if (tryCatch.getExceptionType() == null
+ || classSource.isSuperType(tryCatch.getExceptionType(), task.className).orElse(false)) {
+ Variable exception = tryCatch.getHandler().getExceptionVariable();
+ if (exception != null) {
+ queue.add(new Task(exception.getIndex(), 0, task.className));
+ }
+ break;
+ }
+ }
+
+ continue;
+ }
+
+ List> variableTypes = types.get(task.variable);
+ if (task.degree >= variableTypes.size()) {
+ continue;
+ }
+
+ Set typeSet = variableTypes.get(task.degree);
+ if (!typeSet.add(task.className)) {
+ continue;
+ }
+
+ for (int successor : assignmentGraph.outgoingEdges(task.variable)) {
+ queue.add(new Task(successor, task.degree, task.className));
+ int itemDegree = task.degree + 1;
+ if (itemDegree < variableTypes.size()) {
+ for (String type : variableTypes.get(itemDegree)) {
+ queue.add(new Task(successor, itemDegree, type));
+ }
+ }
+ List> successorVariableTypes = types.get(successor);
+ if (itemDegree < successorVariableTypes.size()) {
+ for (String type : successorVariableTypes.get(itemDegree)) {
+ queue.add(new Task(task.variable, itemDegree, type));
+ }
+ }
+ }
+
+ if (task.degree > 0) {
+ for (int predecessor : assignmentGraph.incomingEdges(task.variable)) {
+ queue.add(new Task(predecessor, task.degree, task.className));
+ }
+
+ for (int successor : itemGraph.outgoingEdges(task.variable)) {
+ queue.add(new Task(successor, task.degree - 1, task.className));
+ }
+ } else {
+ for (int successor : cloneGraph.outgoingEdges(task.variable)) {
+ queue.add(new Task(successor, 0, task.className));
+ }
+
+ IntSet blocks = exceptionMap.get(task.variable);
+ if (blocks != null) {
+ for (int block : blocks.toArray()) {
+ queue.add(new Task(block, -1, task.className));
+ }
+ }
+
+ VirtualCallSite[] callSites = virtualCallSites[task.variable];
+ if (callSites != null) {
+ for (VirtualCallSite callSite : callSites) {
+ MethodReference rawMethod = new MethodReference(task.className,
+ callSite.method.getDescriptor());
+ MethodReader resolvedMethod = classSource.resolve(rawMethod);
+ if (resolvedMethod == null) {
+ continue;
+ }
+ MethodReference resolvedMethodRef = resolvedMethod.getReference();
+ if (callSite.resolvedMethods.add(resolvedMethodRef)) {
+ MethodDependencyInfo methodDep = dependencyInfo.getMethod(resolvedMethodRef);
+ if (callSite.receiver >= 0) {
+ readValue(methodDep.getResult(), program.variableAt(callSite.receiver), queue);
+ }
+ for (int i = 0; i < callSite.arguments.length; ++i) {
+ writeValue(methodDep.getVariable(i + 1), program.variableAt(callSite.arguments[i]),
+ queue);
+ }
+ for (String type : methodDep.getThrown().getTypes()) {
+ queue.add(new Task(callSite.block, -1, type));
+ }
+ }
+ }
+ }
+ }
+
+ for (int successor : arrayGraph.outgoingEdges(task.variable)) {
+ queue.add(new Task(successor, task.degree + 1, task.className));
+ }
+
+ IntObjectMap variableCasts = casts.get(task.variable);
+ if (variableCasts != null) {
+ ValueType type = task.className.startsWith("[")
+ ? ValueType.parse(task.className)
+ : ValueType.object(task.className);
+ for (int target : variableCasts.keys().toArray()) {
+ ValueType targetType = variableCasts.get(target);
+ if (classSource.isSuperType(targetType, type).orElse(false)) {
+ queue.add(new Task(target, 0, task.className));
+ }
+ }
+ }
+ }
+ }
+
+ static class GraphBuildingVisitor extends AbstractInstructionVisitor {
+ DependencyInfo dependencyInfo;
+ GraphBuilder assignmentGraphBuilder;
+ GraphBuilder cloneGraphBuilder;
+ GraphBuilder arrayGraphBuilder;
+ GraphBuilder itemGraphBuilder;
+ List> casts = new ArrayList<>();
+ IntObjectMap exceptionMap = new IntObjectOpenHashMap<>();
+ List tasks = new ArrayList<>();
+ List> virtualCallSites = new ArrayList<>();
+ BasicBlock currentBlock;
+
+ GraphBuildingVisitor(int variableCount, DependencyInfo dependencyInfo) {
+ this.dependencyInfo = dependencyInfo;
+ assignmentGraphBuilder = new GraphBuilder(variableCount);
+ cloneGraphBuilder = new GraphBuilder(variableCount);
+ arrayGraphBuilder = new GraphBuilder(variableCount);
+ itemGraphBuilder = new GraphBuilder(variableCount);
+ casts = new ArrayList<>(variableCount);
+ for (int i = 0; i < variableCount; ++i) {
+ casts.add(new IntObjectOpenHashMap<>());
+ }
+
+ virtualCallSites = new ArrayList<>(Collections.nCopies(variableCount, null));
+ }
+
+ public void visit(Phi phi) {
+ for (Incoming incoming : phi.getIncomings()) {
+ assignmentGraphBuilder.addEdge(incoming.getValue().getIndex(), phi.getReceiver().getIndex());
+ }
+ }
+
+ @Override
+ public void visit(ClassConstantInstruction insn) {
+ tasks.add(new Task(insn.getReceiver().getIndex(), 0, "java.lang.Class"));
+ }
+
+ @Override
+ public void visit(StringConstantInstruction insn) {
+ tasks.add(new Task(insn.getReceiver().getIndex(), 0, "java.lang.String"));
+ }
+
+ @Override
+ public void visit(AssignInstruction insn) {
+ assignmentGraphBuilder.addEdge(insn.getAssignee().getIndex(), insn.getReceiver().getIndex());
+ }
+
+ @Override
+ public void visit(CastInstruction insn) {
+ casts.get(insn.getValue().getIndex()).put(insn.getReceiver().getIndex(), insn.getTargetType());
+ }
+
+ @Override
+ public void visit(RaiseInstruction insn) {
+ IntSet blockIndexes = exceptionMap.get(insn.getException().getIndex());
+ if (blockIndexes == null) {
+ blockIndexes = new IntOpenHashSet();
+ exceptionMap.put(insn.getException().getIndex(), blockIndexes);
+ }
+ blockIndexes.add(currentBlock.getIndex());
+ }
+
+ @Override
+ public void visit(ConstructArrayInstruction insn) {
+ tasks.add(new Task(insn.getReceiver().getIndex(), 0, ValueType.arrayOf(insn.getItemType()).toString()));
+ }
+
+ @Override
+ public void visit(ConstructInstruction insn) {
+ tasks.add(new Task(insn.getReceiver().getIndex(), 0, insn.getType()));
+ }
+
+ @Override
+ public void visit(ConstructMultiArrayInstruction insn) {
+ ValueType type = insn.getItemType();
+ for (int i = 0; i < insn.getDimensions().size(); ++i) {
+ type = ValueType.arrayOf(type);
+ }
+ tasks.add(new Task(insn.getReceiver().getIndex(), 0, type.toString()));
+ }
+
+ @Override
+ public void visit(GetFieldInstruction insn) {
+ FieldDependencyInfo fieldDep = dependencyInfo.getField(insn.getField());
+ ValueDependencyInfo valueDep = fieldDep.getValue();
+ readValue(valueDep, insn.getReceiver(), tasks);
+ }
+
+ @Override
+ public void visit(PutFieldInstruction insn) {
+ FieldDependencyInfo fieldDep = dependencyInfo.getField(insn.getField());
+ ValueDependencyInfo valueDep = fieldDep.getValue();
+ writeValue(valueDep, insn.getValue(), tasks);
+ }
+
+ @Override
+ public void visit(CloneArrayInstruction insn) {
+ cloneGraphBuilder.addEdge(insn.getArray().getIndex(), insn.getReceiver().getIndex());
+ }
+
+ @Override
+ public void visit(UnwrapArrayInstruction insn) {
+ assignmentGraphBuilder.addEdge(insn.getArray().getIndex(), insn.getReceiver().getIndex());
+ }
+
+ @Override
+ public void visit(GetElementInstruction insn) {
+ itemGraphBuilder.addEdge(insn.getArray().getIndex(), insn.getReceiver().getIndex());
+ }
+
+ @Override
+ public void visit(PutElementInstruction insn) {
+ arrayGraphBuilder.addEdge(insn.getValue().getIndex(), insn.getArray().getIndex());
+ }
+
+ @Override
+ public void visit(InvokeInstruction insn) {
+ if (insn.getType() == InvocationType.VIRTUAL) {
+ int instance = insn.getInstance().getIndex();
+ List callSites = virtualCallSites.get(instance);
+ if (callSites == null) {
+ callSites = new ArrayList<>();
+ virtualCallSites.set(instance, callSites);
+ }
+
+ VirtualCallSite callSite = new VirtualCallSite();
+ callSite.method = insn.getMethod();
+ callSite.arguments = new int[insn.getArguments().size()];
+ for (int i = 0; i < insn.getArguments().size(); ++i) {
+ callSite.arguments[i] = insn.getArguments().get(i).getIndex();
+ }
+ callSite.receiver = insn.getReceiver() != null ? insn.getReceiver().getIndex() : -1;
+ callSite.block = currentBlock.getIndex();
+ callSites.add(callSite);
+
+ return;
+ }
+
+ MethodDependencyInfo methodDep = dependencyInfo.getMethod(insn.getMethod());
+
+ if (insn.getReceiver() != null) {
+ readValue(methodDep.getResult(), insn.getReceiver(), tasks);
+ }
+
+ for (int i = 0; i < insn.getArguments().size(); ++i) {
+ writeValue(methodDep.getVariable(i + 1), insn.getArguments().get(i), tasks);
+ }
+
+ for (String type : methodDep.getThrown().getTypes()) {
+ tasks.add(new Task(currentBlock.getIndex(), -1, type));
+ }
+ }
+ }
+
+ private static void readValue(ValueDependencyInfo valueDep, Variable receiver, Collection tasks) {
+ int depth = 0;
+ boolean hasArrayType;
+ do {
+ for (String type : valueDep.getTypes()) {
+ tasks.add(new Task(receiver.getIndex(), depth, type));
+ }
+ depth++;
+ hasArrayType = valueDep.hasArrayType();
+ valueDep = valueDep.getArrayItem();
+ } while (hasArrayType);
+ }
+
+ private static void writeValue(ValueDependencyInfo valueDep, Variable source, Collection tasks) {
+ int depth = 0;
+ while (valueDep.hasArrayType()) {
+ depth++;
+ valueDep = valueDep.getArrayItem();
+ for (String type : valueDep.getTypes()) {
+ tasks.add(new Task(source.getIndex(), depth, type));
+ }
+ }
+ }
+
+ static class Task {
+ int variable;
+ int degree;
+ String className;
+
+ Task(int variable, int degree, String className) {
+ this.variable = variable;
+ this.degree = degree;
+ this.className = className;
+ }
+ }
+
+ static class VirtualCallSite {
+ Set resolvedMethods = new HashSet<>();
+ MethodReference method;
+ int[] arguments;
+ int receiver;
+ int block;
+ }
+}
diff --git a/tests/src/test/java/org/teavm/dependency/DependencyTest.java b/tests/src/test/java/org/teavm/dependency/DependencyTest.java
new file mode 100644
index 000000000..12246701a
--- /dev/null
+++ b/tests/src/test/java/org/teavm/dependency/DependencyTest.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2017 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 com.carrotsearch.hppc.IntOpenHashSet;
+import com.carrotsearch.hppc.IntSet;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.teavm.backend.javascript.JavaScriptTarget;
+import org.teavm.common.DisjointSet;
+import org.teavm.diagnostics.Problem;
+import org.teavm.model.BasicBlock;
+import org.teavm.model.ClassHolderSource;
+import org.teavm.model.Instruction;
+import org.teavm.model.MethodHolder;
+import org.teavm.model.MethodReference;
+import org.teavm.model.Program;
+import org.teavm.model.TextLocation;
+import org.teavm.model.ValueType;
+import org.teavm.model.analysis.ClassInference;
+import org.teavm.model.instructions.AbstractInstructionVisitor;
+import org.teavm.model.instructions.AssignInstruction;
+import org.teavm.model.instructions.ClassConstantInstruction;
+import org.teavm.model.instructions.InvokeInstruction;
+import org.teavm.model.instructions.PutElementInstruction;
+import org.teavm.model.instructions.UnwrapArrayInstruction;
+import org.teavm.parsing.ClasspathClassHolderSource;
+import org.teavm.vm.TeaVM;
+import org.teavm.vm.TeaVMBuilder;
+import org.teavm.vm.TeaVMPhase;
+import org.teavm.vm.TeaVMProgressFeedback;
+import org.teavm.vm.TeaVMProgressListener;
+
+public class DependencyTest {
+ @Rule
+ public final TestName testName = new TestName();
+
+ private static ClassHolderSource classSource;
+
+ @BeforeClass
+ public static void prepare() {
+ classSource = new ClasspathClassHolderSource(DependencyTest.class.getClassLoader());
+ }
+
+ @AfterClass
+ public static void cleanup() {
+ classSource = null;
+ }
+
+ @Test
+ public void virtualCall() {
+ doTest();
+ }
+
+ @Test
+ public void instanceOf() {
+ doTest();
+ }
+
+ @Test
+ public void catchException() {
+ doTest();
+ }
+
+ @Test
+ public void propagateException() {
+ doTest();
+ }
+
+ @Test
+ public void arrays() {
+ doTest();
+ }
+
+ @Test
+ public void arraysPassed() {
+ doTest();
+ }
+
+ @Test
+ public void arraysRetrieved() {
+ doTest();
+ }
+
+ private void doTest() {
+ TeaVM vm = new TeaVMBuilder(new JavaScriptTarget())
+ .setClassLoader(DependencyTest.class.getClassLoader())
+ .setClassSource(classSource)
+ .build();
+ vm.setProgressListener(new TeaVMProgressListener() {
+ @Override
+ public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) {
+ return phase == TeaVMPhase.DEPENDENCY_CHECKING
+ ? TeaVMProgressFeedback.CONTINUE
+ : TeaVMProgressFeedback.CANCEL;
+ }
+
+ @Override
+ public TeaVMProgressFeedback progressReached(int progress) {
+ return TeaVMProgressFeedback.CONTINUE;
+ }
+ });
+ vm.installPlugins();
+
+ MethodReference testMethod = new MethodReference(DependencyTestData.class,
+ testName.getMethodName(), void.class);
+ vm.entryPoint(testMethod).withValue(0, DependencyTestData.class.getName());
+ vm.build(fileName -> new ByteArrayOutputStream(), "out");
+
+ List problems = vm.getProblemProvider().getSevereProblems();
+ if (!problems.isEmpty()) {
+ Problem problem = problems.get(0);
+ Assert.fail("Error at " + problem.getLocation().getSourceLocation() + ": " + problem.getText());
+ }
+
+ MethodHolder method = classSource.get(testMethod.getClassName()).getMethod(testMethod.getDescriptor());
+ List assertions = collectAssertions(method);
+ processAssertions(assertions, vm.getDependencyInfo().getMethod(testMethod), vm.getDependencyInfo(),
+ method.getProgram());
+ }
+
+ private void processAssertions(List assertions, MethodDependencyInfo methodDep,
+ DependencyInfo dependencyInfo, Program program) {
+ ClassInference classInference = new ClassInference(dependencyInfo);
+ classInference.infer(program, methodDep.getReference());
+
+ for (Assertion assertion : assertions) {
+ ValueDependencyInfo valueDep = methodDep.getVariable(assertion.value);
+ String[] actualTypes = valueDep.getTypes();
+ String[] expectedTypes = assertion.expectedTypes.clone();
+ Arrays.sort(actualTypes);
+ Arrays.sort(expectedTypes);
+ Assert.assertArrayEquals("Assertion at " + assertion.location, expectedTypes, actualTypes);
+
+ actualTypes = classInference.classesOf(assertion.value);
+ Arrays.sort(actualTypes);
+ Assert.assertArrayEquals("Assertion at " + assertion.location + " (class inference)",
+ expectedTypes, actualTypes);
+ }
+ }
+
+ private List collectAssertions(MethodHolder method) {
+ Program program = method.getProgram();
+
+ AliasCollector aliasCollector = new AliasCollector(program.variableCount());
+ for (BasicBlock block : program.getBasicBlocks()) {
+ for (Instruction instruction : block) {
+ instruction.acceptVisitor(aliasCollector);
+ }
+ }
+ int[] aliases = aliasCollector.disjointSet.pack(program.variableCount());
+
+ AssertionCollector assertionCollector = new AssertionCollector(aliases);
+ for (BasicBlock block : program.getBasicBlocks()) {
+ for (Instruction instruction : block) {
+ instruction.acceptVisitor(assertionCollector);
+ }
+ }
+ assertionCollector.postProcess();
+
+ return assertionCollector.assertions;
+ }
+
+ static class AliasCollector extends AbstractInstructionVisitor {
+ DisjointSet disjointSet = new DisjointSet();
+
+ AliasCollector(int variableCount) {
+ for (int i = 0; i < variableCount; ++i) {
+ disjointSet.create();
+ }
+ }
+
+ @Override
+ public void visit(AssignInstruction insn) {
+ disjointSet.union(insn.getReceiver().getIndex(), insn.getAssignee().getIndex());
+ }
+
+ @Override
+ public void visit(UnwrapArrayInstruction insn) {
+ disjointSet.union(insn.getReceiver().getIndex(), insn.getArray().getIndex());
+ }
+ }
+
+ static class AssertionCollector extends AbstractInstructionVisitor {
+ static final MethodReference assertionMethod = new MethodReference(MetaAssertions.class,
+ "assertTypes", Object.class, Class[].class, void.class);
+ int[] aliases;
+ List assertions = new ArrayList<>();
+ IntSet[] arrayContent;
+ ValueType[] classConstants;
+
+ AssertionCollector(int[] aliases) {
+ this.aliases = aliases;
+ classConstants = new ValueType[aliases.length];
+ arrayContent = new IntSet[aliases.length];
+ }
+
+ void postProcess() {
+ int[] aliasInstances = new int[aliases.length];
+ Arrays.fill(aliasInstances, -1);
+ for (int i = 0; i < aliases.length; ++i) {
+ int alias = aliases[i];
+ if (aliasInstances[alias] < 0) {
+ aliasInstances[alias] = i;
+ }
+ }
+
+ for (Assertion assertion : assertions) {
+ IntSet items = arrayContent[assertion.array];
+ if (items != null) {
+ Set expectedClasses = new HashSet<>();
+ for (int item : items.toArray()) {
+ ValueType constant = classConstants[item];
+ if (constant != null) {
+ String expectedClass;
+ if (constant instanceof ValueType.Object) {
+ expectedClass = ((ValueType.Object) constant).getClassName();
+ } else {
+ expectedClass = constant.toString();
+ }
+ expectedClasses.add(expectedClass);
+ }
+ }
+ assertion.expectedTypes = expectedClasses.toArray(new String[0]);
+ } else {
+ assertion.expectedTypes = new String[0];
+ }
+
+ assertion.value = aliasInstances[assertion.value];
+ }
+ }
+
+ @Override
+ public void visit(InvokeInstruction insn) {
+ if (insn.getMethod().equals(assertionMethod)) {
+ Assertion assertion = new Assertion();
+ assertion.value = aliases[insn.getArguments().get(0).getIndex()];
+ assertion.array = aliases[insn.getArguments().get(1).getIndex()];
+ assertion.location = insn.getLocation();
+ assertions.add(assertion);
+ }
+ }
+
+ @Override
+ public void visit(ClassConstantInstruction insn) {
+ classConstants[aliases[insn.getReceiver().getIndex()]] = insn.getConstant();
+ }
+
+ @Override
+ public void visit(PutElementInstruction insn) {
+ int array = aliases[insn.getArray().getIndex()];
+ int value = aliases[insn.getValue().getIndex()];
+ IntSet items = arrayContent[array];
+ if (items == null) {
+ items = new IntOpenHashSet();
+ arrayContent[array] = items;
+ }
+ items.add(value);
+ }
+ }
+
+ private static class Assertion {
+ int value;
+ int array;
+ TextLocation location;
+ String[] expectedTypes;
+ }
+}
diff --git a/tests/src/test/java/org/teavm/dependency/DependencyTestData.java b/tests/src/test/java/org/teavm/dependency/DependencyTestData.java
new file mode 100644
index 000000000..ac548cbde
--- /dev/null
+++ b/tests/src/test/java/org/teavm/dependency/DependencyTestData.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2017 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;
+
+public class DependencyTestData {
+ public void virtualCall() {
+ MetaAssertions.assertTypes(getI(0).foo(), String.class, Integer.class, Class.class);
+ }
+
+ public void instanceOf() {
+ MetaAssertions.assertTypes((String) getI(0).foo(), String.class);
+ }
+
+ public void catchException() throws Exception {
+ try {
+ throw createException(0);
+ } catch (IndexOutOfBoundsException e) {
+ MetaAssertions.assertTypes(e, IndexOutOfBoundsException.class);
+ } catch (RuntimeException e) {
+ MetaAssertions.assertTypes(e, UnsupportedOperationException.class, IllegalArgumentException.class);
+ }
+ }
+
+ public void propagateException() {
+ try {
+ catchException();
+ } catch (Throwable e) {
+ MetaAssertions.assertTypes(e, Exception.class);
+ }
+ }
+
+ public void arrays() {
+ Object[] array = { new String("123"), new Integer(123), String.class };
+ MetaAssertions.assertTypes(array[0], String.class, Integer.class, Class.class);
+ }
+
+ public void arraysPassed() {
+ Object[] array = new Object[3];
+ fillArray(array);
+ MetaAssertions.assertTypes(array[0], String.class, Integer.class, Class.class);
+
+ Object[] array2 = new Object[3];
+ staticArrayField = array2;
+ fillStaticArray();
+ MetaAssertions.assertTypes(array2[0], Long.class, RuntimeException.class);
+ }
+
+ public void arraysRetrieved() {
+ Object[] array = createArray();
+ MetaAssertions.assertTypes(array[0], String.class, Integer.class, Class.class);
+
+ staticArrayField = new Object[3];
+ Object[] array2 = staticArrayField;
+ fillStaticArray();
+ MetaAssertions.assertTypes(array2[0], Long.class, RuntimeException.class);
+ }
+
+ static Object[] staticArrayField;
+
+ private Object[] createArray() {
+ Object[] array = new Object[3];
+ fillArray(array);
+ return array;
+ }
+
+ private void fillArray(Object[] array) {
+ array[0] = "123";
+ array[1] = 123;
+ array[2] = String.class;
+ }
+
+ private void fillStaticArray() {
+ staticArrayField[0] = 42L;
+ staticArrayField[0] = new RuntimeException();
+ }
+
+ private I getI(int index) {
+ switch (index) {
+ case 0:
+ return new A();
+ case 1:
+ return new B();
+ default:
+ return new C();
+ }
+ }
+
+ private Exception createException(int index) {
+ switch (index) {
+ case 0:
+ throw new IndexOutOfBoundsException();
+ case 1:
+ throw new UnsupportedOperationException();
+ case 2:
+ throw new IllegalArgumentException();
+ default:
+ return new Exception();
+ }
+ }
+
+ interface I {
+ Object foo();
+ }
+
+ class A implements I {
+ @Override
+ public Object foo() {
+ return "123";
+ }
+ }
+
+ class B implements I {
+ @Override
+ public Object foo() {
+ return Object.class;
+ }
+ }
+
+ class C implements I {
+ @Override
+ public Object foo() {
+ return 123;
+ }
+ }
+}
diff --git a/tests/src/test/java/org/teavm/dependency/MetaAssertions.java b/tests/src/test/java/org/teavm/dependency/MetaAssertions.java
new file mode 100644
index 000000000..4ec555c61
--- /dev/null
+++ b/tests/src/test/java/org/teavm/dependency/MetaAssertions.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 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;
+
+public final class MetaAssertions {
+ private MetaAssertions() {
+ }
+
+ public static void assertTypes(Object value, Class>... types) {
+ // do nothing, process by TeaVM test infrastructure
+ }
+}