org.teavm.dependency.DependencyGraphBuilder Maven / Gradle / Ivy
/*
* Copyright 2012 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 static org.teavm.dependency.AbstractInstructionAnalyzer.MONITOR_ENTER_METHOD;
import static org.teavm.dependency.AbstractInstructionAnalyzer.MONITOR_ENTER_SYNC_METHOD;
import static org.teavm.dependency.AbstractInstructionAnalyzer.MONITOR_EXIT_METHOD;
import static org.teavm.dependency.AbstractInstructionAnalyzer.MONITOR_EXIT_SYNC_METHOD;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.teavm.model.AccessLevel;
import org.teavm.model.BasicBlockReader;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.IncomingReader;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.PhiReader;
import org.teavm.model.Program;
import org.teavm.model.TryCatchBlockReader;
import org.teavm.model.ValueType;
import org.teavm.model.VariableReader;
import org.teavm.model.instructions.ArrayElementType;
import org.teavm.model.text.ListingBuilder;
class DependencyGraphBuilder {
private static final MethodDescriptor GET_CLASS = new MethodDescriptor("getClass", Class.class);
private DependencyAnalyzer dependencyAnalyzer;
private DependencyNode[] nodes;
private DependencyNode resultNode;
private Program program;
private DefaultCallGraphNode caller;
private ExceptionConsumer currentExceptionConsumer;
DependencyGraphBuilder(DependencyAnalyzer dependencyAnalyzer) {
this.dependencyAnalyzer = dependencyAnalyzer;
}
public void buildGraph(MethodDependency dep) {
caller = dependencyAnalyzer.callGraph.getNode(dep.getReference());
MethodHolder method = dep.method;
if (method.getProgram() == null || method.getProgram().basicBlockCount() == 0) {
return;
}
program = method.getProgram();
resultNode = dep.getResult();
DataFlowGraphBuilder dfgBuilder = new DataFlowGraphBuilder();
boolean[] significantParams = new boolean[dep.getParameterCount()];
significantParams[0] = true;
for (int i = 1; i < dep.getParameterCount(); ++i) {
ValueType arg = method.parameterType(i - 1);
if (!(arg instanceof ValueType.Primitive)) {
significantParams[i] = true;
}
}
int[] nodeMapping = dfgBuilder.buildMapping(program, significantParams,
!(method.getResultType() instanceof ValueType.Primitive) && method.getResultType() != ValueType.VOID);
if (DependencyAnalyzer.shouldLog) {
System.out.println("Method reached: " + method.getReference());
System.out.print(new ListingBuilder().buildListing(program, " "));
for (int i = 0; i < nodeMapping.length; ++i) {
System.out.print(i + ":" + nodeMapping[i] + " ");
}
System.out.println();
System.out.println();
}
int nodeClassCount = 0;
for (int i = 0; i < nodeMapping.length; ++i) {
nodeClassCount = Math.max(nodeClassCount, nodeMapping[i] + 1);
}
DependencyNode[] nodeClasses = Arrays.copyOf(dep.getVariables(), nodeClassCount);
MethodReference ref = method.getReference();
for (int i = dep.getVariableCount(); i < nodeClasses.length; ++i) {
nodeClasses[i] = dependencyAnalyzer.createNode();
nodeClasses[i].method = ref;
if (DependencyAnalyzer.shouldTag) {
nodeClasses[i].setTag(dep.getMethod().getReference() + ":" + i);
}
}
nodes = new DependencyNode[dep.getMethod().getProgram().variableCount()];
for (int i = 0; i < nodes.length; ++i) {
int mappedNode = nodeMapping[i];
nodes[i] = mappedNode >= 0 ? nodeClasses[mappedNode] : null;
}
dep.setVariables(nodes);
reader.setCaller(caller.getMethod());
for (int i = 0; i < program.basicBlockCount(); ++i) {
BasicBlockReader block = program.basicBlockAt(i);
currentExceptionConsumer = createExceptionConsumer(dep, block);
block.readAllInstructions(reader);
for (PhiReader phi : block.readPhis()) {
DependencyNode receiverNode = nodes[phi.getReceiver().getIndex()];
for (IncomingReader incoming : phi.readIncomings()) {
DependencyNode incomingNode = nodes[incoming.getValue().getIndex()];
if (incomingNode != null && receiverNode != null) {
incomingNode.connect(receiverNode);
}
}
}
for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) {
if (tryCatch.getExceptionType() != null) {
dependencyAnalyzer.linkClass(tryCatch.getExceptionType());
}
}
}
if (method.hasModifier(ElementModifier.SYNCHRONIZED)) {
List syncNodes = new ArrayList<>();
MethodDependency methodDep;
if (dependencyAnalyzer.asyncSupported) {
methodDep = dependencyAnalyzer.linkMethod(MONITOR_ENTER_METHOD);
syncNodes.add(methodDep.getVariable(1));
methodDep.use();
}
methodDep = dependencyAnalyzer.linkMethod(MONITOR_ENTER_SYNC_METHOD);
syncNodes.add(methodDep.getVariable(1));
methodDep.use();
if (dependencyAnalyzer.asyncSupported) {
methodDep = dependencyAnalyzer.linkMethod(MONITOR_EXIT_METHOD);
syncNodes.add(methodDep.getVariable(1));
methodDep.use();
}
methodDep = dependencyAnalyzer.linkMethod(MONITOR_EXIT_SYNC_METHOD);
syncNodes.add(methodDep.getVariable(1));
methodDep.use();
if (method.hasModifier(ElementModifier.STATIC)) {
for (DependencyNode node : syncNodes) {
node.propagate(dependencyAnalyzer.getType("java.lang.Class"));
}
} else {
for (DependencyNode node : syncNodes) {
nodes[0].connect(node);
}
}
}
}
private ExceptionConsumer createExceptionConsumer(MethodDependency methodDep, BasicBlockReader block) {
List extends TryCatchBlockReader> tryCatchBlocks = block.readTryCatchBlocks();
ClassReader[] exceptions = new ClassReader[tryCatchBlocks.size()];
DependencyNode[] vars = new DependencyNode[tryCatchBlocks.size()];
for (int i = 0; i < tryCatchBlocks.size(); ++i) {
TryCatchBlockReader tryCatch = tryCatchBlocks.get(i);
if (tryCatch.getExceptionType() != null) {
exceptions[i] = dependencyAnalyzer.getClassSource().get(tryCatch.getExceptionType());
}
if (tryCatch.getHandler().getExceptionVariable() != null) {
vars[i] = methodDep.getVariable(tryCatch.getHandler().getExceptionVariable().getIndex());
}
}
return new ExceptionConsumer(dependencyAnalyzer, exceptions, vars, methodDep);
}
static class ExceptionConsumer implements DependencyConsumer {
private DependencyAnalyzer analyzer;
private ClassReader[] exceptions;
private DependencyNode[] vars;
private MethodDependency method;
ExceptionConsumer(DependencyAnalyzer analyzer, ClassReader[] exceptions, DependencyNode[] vars,
MethodDependency method) {
this.analyzer = analyzer;
this.exceptions = exceptions;
this.vars = vars;
this.method = method;
}
@Override
public void consume(DependencyType type) {
ClassHierarchy hierarchy = analyzer.getClassHierarchy();
for (int i = 0; i < exceptions.length; ++i) {
if (exceptions[i] == null || hierarchy.isSuperType(exceptions[i].getName(), type.getName(), false)) {
if (vars[i] != null) {
vars[i].propagate(type);
}
return;
}
}
method.getThrown().propagate(type);
}
}
private AbstractInstructionAnalyzer reader = new AbstractInstructionAnalyzer() {
@Override
public void assign(VariableReader receiver, VariableReader assignee) {
DependencyNode valueNode = nodes[assignee.getIndex()];
DependencyNode receiverNode = nodes[receiver.getIndex()];
if (valueNode != null && receiverNode != null) {
valueNode.connect(receiverNode);
}
}
@Override
public void cast(VariableReader receiver, VariableReader value, ValueType targetType) {
super.cast(receiver, value, targetType);
currentExceptionConsumer.consume(dependencyAnalyzer.getType("java.lang.ClassCastException"));
DependencyNode valueNode = nodes[value.getIndex()];
DependencyNode receiverNode = nodes[receiver.getIndex()];
ClassReaderSource classSource = dependencyAnalyzer.getClassSource();
if (targetType instanceof ValueType.Object) {
String targetClsName = ((ValueType.Object) targetType).getClassName();
ClassReader targetClass = classSource.get(targetClsName);
if (targetClass != null && !(targetClass.getName().equals("java.lang.Object"))) {
if (valueNode != null && receiverNode != null) {
valueNode.connect(receiverNode, dependencyAnalyzer.getSuperClassFilter(targetClass.getName()));
}
return;
}
} else if (targetType instanceof ValueType.Array) {
ValueType itemType = targetType;
while (itemType instanceof ValueType.Array) {
itemType = ((ValueType.Array) itemType).getItemType();
}
if (itemType instanceof ValueType.Object) {
ClassReader targetClass = classSource.get(((ValueType.Object) itemType).getClassName());
if (targetClass == null) {
valueNode.connect(receiverNode);
return;
}
}
if (valueNode != null && receiverNode != null) {
valueNode.connect(receiverNode, dependencyAnalyzer.getSuperClassFilter(targetType.toString()));
}
return;
}
if (valueNode != null && receiverNode != null) {
valueNode.connect(receiverNode);
}
}
@Override
public void exit(VariableReader valueToReturn) {
if (valueToReturn != null) {
DependencyNode node = nodes[valueToReturn.getIndex()];
if (node != null) {
node.connect(resultNode);
}
}
}
@Override
public void raise(VariableReader exception) {
nodes[exception.getIndex()].addConsumer(currentExceptionConsumer);
}
@Override
public void unwrapArray(VariableReader receiver, VariableReader array, ArrayElementType elementType) {
DependencyNode arrayNode = nodes[array.getIndex()];
DependencyNode receiverNode = nodes[receiver.getIndex()];
if (arrayNode != null && receiverNode != null) {
arrayNode.connect(receiverNode);
}
}
@Override
public void cloneArray(VariableReader receiver, VariableReader array) {
DependencyNode arrayNode = getNode(array);
DependencyNode receiverNode = getNode(receiver);
if (arrayNode != null && receiverNode != null) {
arrayNode.addConsumer(receiverNode::propagate);
arrayNode.getArrayItem().connect(receiverNode.getArrayItem());
}
MethodDependency cloneDep = getAnalyzer().linkMethod(CLONE_METHOD);
cloneDep.addLocation(getCallLocation());
if (arrayNode != null) {
arrayNode.connect(cloneDep.getVariable(0));
}
cloneDep.use();
}
@Override
public void getElement(VariableReader receiver, VariableReader array, VariableReader index,
ArrayElementType type) {
if (isPrimitive(type)) {
return;
}
DependencyNode arrayNode = nodes[array.getIndex()];
DependencyNode receiverNode = nodes[receiver.getIndex()];
if (arrayNode != null && receiverNode != null && receiverNode != arrayNode.getArrayItem()) {
arrayNode.getArrayItem().connect(receiverNode);
}
}
@Override
public void putElement(VariableReader array, VariableReader index, VariableReader value,
ArrayElementType type) {
if (isPrimitive(type)) {
return;
}
DependencyNode valueNode = nodes[value.getIndex()];
DependencyNode arrayNode = nodes[array.getIndex()];
if (valueNode != null && arrayNode != null && valueNode != arrayNode.getArrayItem()) {
valueNode.connect(arrayNode.getArrayItem());
}
}
private boolean isPrimitive(ArrayElementType type) {
return type != ArrayElementType.OBJECT;
}
@Override
protected void invokeSpecial(VariableReader receiver, VariableReader instance, MethodReference method,
List extends VariableReader> arguments) {
if (handleSpecialMethod(receiver, instance, method)) {
return;
}
CallLocation callLocation = getCallLocation();
if (instance == null) {
dependencyAnalyzer.linkClass(method.getClassName()).initClass(callLocation);
} else {
dependencyAnalyzer.linkClass(method.getClassName());
}
MethodDependency methodDep = dependencyAnalyzer.linkMethod(method);
methodDep.addLocation(callLocation);
methodDep.use(false);
if (methodDep.isMissing()) {
return;
}
DependencyNode[] targetParams = methodDep.getVariables();
for (int i = 0; i < arguments.size(); ++i) {
DependencyNode value = nodes[arguments.get(i).getIndex()];
DependencyNode param = targetParams[i + 1];
if (value != null && param != null) {
value.connect(param);
}
}
if (instance != null) {
nodes[instance.getIndex()].connect(targetParams[0]);
}
if (methodDep.getResult() != null && receiver != null) {
DependencyNode receiverNode = nodes[receiver.getIndex()];
if (methodDep.getResult() != null && receiverNode != null) {
methodDep.getResult().connect(receiverNode);
}
}
methodDep.getThrown().addConsumer(currentExceptionConsumer);
initClass(method.getClassName());
}
@Override
protected void invokeVirtual(VariableReader receiver, VariableReader instance, MethodReference method,
List extends VariableReader> arguments) {
ClassReader cls = dependencyAnalyzer.getClassSource().get(method.getClassName());
if (cls != null) {
MethodReader methodHolder = cls.getMethod(method.getDescriptor());
if (methodHolder != null && methodHolder.getLevel() == AccessLevel.PRIVATE) {
invokeSpecial(receiver, instance, method, arguments);
return;
}
}
if (handleSpecialMethod(receiver, instance, method)) {
return;
}
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] = getNode(instance);
DependencyConsumer listener = new VirtualCallConsumer(
method.getClassName(), method.getDescriptor(), dependencyAnalyzer, actualArgs,
receiver != null ? getNode(receiver) : null, getCallLocation(),
currentExceptionConsumer);
getNode(instance).addConsumer(listener);
dependencyAnalyzer.getClassSource().overriddenMethods(method).forEach(methodImpl -> {
dependencyAnalyzer.linkMethod(methodImpl.getReference()).addLocation(getCallLocation());
});
}
private boolean handleSpecialMethod(VariableReader receiver, VariableReader instance, MethodReference method) {
if (method.getDescriptor().equals(GET_CLASS)) {
invokeGetClass(receiver, instance);
return true;
} else if (method.getClassName().equals("java.lang.Class")) {
switch (method.getName()) {
case "getComponentType":
invokeGetComponentType(receiver, instance, method);
return true;
case "getSuperclass":
invokeGetSuperclass(receiver, instance, method);
return true;
case "getInterfaces":
invokeGetInterfaces(receiver, instance, method);
return true;
}
}
return false;
}
private void invokeGetClass(VariableReader receiver, VariableReader instance) {
MethodDependency getClassDep = dependencyAnalyzer.linkMethod("java.lang.Object", GET_CLASS);
getClassDep.addLocation(getCallLocation());
getNode(instance).addConsumer(t -> {
getClassDep.getVariable(0).propagate(t);
if (receiver != null) {
getNode(receiver).getClassValueNode().propagate(t);
}
});
if (receiver != null) {
getNode(receiver).propagate(dependencyAnalyzer.getType("java.lang.Class"));
}
getClassDep.use();
}
private void invokeGetComponentType(VariableReader receiver, VariableReader instance,
MethodReference methodReference) {
MethodDependency methodDep = dependencyAnalyzer.linkMethod(methodReference);
methodDep.use();
DependencyNode instanceNode = getNode(instance);
DependencyNode receiverNode = getNode(receiver);
receiverNode.propagate(dependencyAnalyzer.classType);
instanceNode.getClassValueNode().addConsumer(t -> {
if (!t.getName().startsWith("[")) {
return;
}
String typeName = t.getName().substring(1);
if (typeName.charAt(0) == 'L') {
typeName = ((ValueType.Object) ValueType.parse(typeName)).getClassName();
}
receiverNode.getClassValueNode().propagate(dependencyAnalyzer.getType(typeName));
methodDep.getVariable(0).propagate(t);
});
}
private void invokeGetSuperclass(VariableReader receiver, VariableReader instance,
MethodReference methodReference) {
MethodDependency methodDep = dependencyAnalyzer.linkMethod(methodReference);
methodDep.use();
DependencyNode instanceNode = getNode(instance);
DependencyNode receiverNode = getNode(receiver);
receiverNode.propagate(dependencyAnalyzer.classType);
instanceNode.getClassValueNode().addConsumer(type -> {
String className = type.getName();
if (className.startsWith("[")) {
return;
}
ClassReader cls = dependencyAnalyzer.getClassSource().get(className);
if (cls != null && cls.getParent() != null) {
receiverNode.getClassValueNode().propagate(dependencyAnalyzer.getType(cls.getParent()));
}
methodDep.getVariable(0).propagate(type);
});
}
private void invokeGetInterfaces(VariableReader receiver, VariableReader instance,
MethodReference methodReference) {
MethodDependency methodDep = dependencyAnalyzer.linkMethod(methodReference);
methodDep.use();
DependencyNode instanceNode = getNode(instance);
DependencyNode receiverNode = getNode(receiver);
receiverNode.propagate(dependencyAnalyzer.getType("[java/lang/Class;"));
receiverNode.getArrayItem().propagate(dependencyAnalyzer.classType);
instanceNode.getArrayItem().getClassValueNode().addConsumer(type -> {
String className = type.getName();
if (className.startsWith("[")) {
return;
}
ClassReader cls = dependencyAnalyzer.getClassSource().get(className);
if (cls != null) {
for (String iface : cls.getInterfaces()) {
receiverNode.getClassValueNode().propagate(dependencyAnalyzer.getType(iface));
}
}
methodDep.getVariable(0).propagate(type);
});
}
@Override
public void nullCheck(VariableReader receiver, VariableReader value) {
super.nullCheck(receiver, value);
currentExceptionConsumer.consume(dependencyAnalyzer.getType("java.lang.NullPointerException"));
}
@Override
protected DependencyNode getNode(VariableReader variable) {
return nodes[variable.getIndex()];
}
@Override
protected DependencyAnalyzer getAnalyzer() {
return dependencyAnalyzer;
}
};
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy