All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.teavm.dependency.DependencyGraphBuilder Maven / Gradle / Ivy

There is a newer version: 0.10.2
Show newest version
/*
 *  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 java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.teavm.callgraph.DefaultCallGraphNode;
import org.teavm.model.*;
import org.teavm.model.instructions.*;
import org.teavm.model.util.ListingBuilder;

/**
 *
 * @author Alexey Andreev
 */
class DependencyGraphBuilder {
    private DependencyChecker dependencyChecker;
    private DependencyNode[] nodes;
    private DependencyNode resultNode;
    private ProgramReader program;
    private DefaultCallGraphNode caller;
    private InstructionLocation currentLocation;
    private ExceptionConsumer currentExceptionConsumer;

    public DependencyGraphBuilder(DependencyChecker dependencyChecker) {
        this.dependencyChecker = dependencyChecker;
    }

    public void buildGraph(MethodDependency dep) {
        caller = dependencyChecker.callGraph.getNode(dep.getReference());
        MethodReader method = dep.getMethod();
        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 (DependencyChecker.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);
        for (int i = dep.getVariableCount(); i < nodeClasses.length; ++i) {
            nodeClasses[i] = dependencyChecker.createNode();
            if (DependencyChecker.shouldLog) {
                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);

        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()) {
                for (IncomingReader incoming : phi.readIncomings()) {
                    DependencyNode incomingNode = nodes[incoming.getValue().getIndex()];
                    DependencyNode receiverNode = nodes[phi.getReceiver().getIndex()];
                    if (incomingNode != null && receiverNode != null) {
                        incomingNode.connect(receiverNode);
                    }
                }
            }
            for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) {
                if (tryCatch.getExceptionType() != null) {
                    dependencyChecker.linkClass(tryCatch.getExceptionType(), new CallLocation(caller.getMethod()));
                }
            }
        }

        if (method.hasModifier(ElementModifier.SYNCHRONIZED)) {
            List syncNodes = new ArrayList<>();

            MethodDependency methodDep = dependencyChecker.linkMethod(
                        new MethodReference(Object.class, "monitorEnter", Object.class, void.class), null);
            syncNodes.add(methodDep.getVariable(1));
            methodDep.use();

            methodDep = dependencyChecker.linkMethod(
                    new MethodReference(Object.class, "monitorEnterSync", Object.class, void.class), null);
            syncNodes.add(methodDep.getVariable(1));
            methodDep.use();

            methodDep = dependencyChecker.linkMethod(
                    new MethodReference(Object.class, "monitorExit", Object.class, void.class), null);
            syncNodes.add(methodDep.getVariable(1));
            methodDep.use();

            methodDep = dependencyChecker.linkMethod(
                    new MethodReference(Object.class, "monitorExitSync", Object.class, void.class), null);
            syncNodes.add(methodDep.getVariable(1));
            methodDep.use();

            if (method.hasModifier(ElementModifier.STATIC)) {
                for (DependencyNode node : syncNodes) {
                    node.propagate(dependencyChecker.getType("java.lang.Class"));
                }
            } else {
                for (DependencyNode node : syncNodes) {
                    nodes[0].connect(node);
                }
            }
        }
    }

    private ExceptionConsumer createExceptionConsumer(MethodDependency methodDep, BasicBlockReader block) {
        List 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] = dependencyChecker.getClassSource().get(tryCatch.getExceptionType());
            }
            vars[i] = methodDep.getVariable(tryCatch.getExceptionVariable().getIndex());
        }
        return new ExceptionConsumer(dependencyChecker, exceptions, vars, methodDep);
    }

    private static class ExceptionConsumer implements DependencyConsumer {
        private DependencyChecker checker;
        private ClassReader[] exceptions;
        private DependencyNode[] vars;
        private MethodDependency method;

        public ExceptionConsumer(DependencyChecker checker, ClassReader[] exceptions, DependencyNode[] vars,
                MethodDependency method) {
            this.checker = checker;
            this.exceptions = exceptions;
            this.vars = vars;
            this.method = method;
        }

        @Override
        public void consume(DependencyType type) {
            for (int i = 0; i < exceptions.length; ++i) {
                if (exceptions[i] == null || isAssignableFrom(checker.getClassSource(), exceptions[i].getName(),
                        type.getName())) {
                    if (vars[i] != null) {
                        vars[i].propagate(type);
                    }
                    return;
                }
            }
            method.getThrown().propagate(type);
        }
    }

    private static class VirtualCallConsumer implements DependencyConsumer {
        private final DependencyNode node;
        private final ClassReader filterClass;
        private final MethodDescriptor methodDesc;
        private final DependencyChecker checker;
        private final DependencyNode[] parameters;
        private final DependencyNode result;
        private final DefaultCallGraphNode caller;
        private final InstructionLocation location;
        private final Set knownMethods = new HashSet<>();
        private ExceptionConsumer exceptionConsumer;

        public VirtualCallConsumer(DependencyNode node, ClassReader filterClass,
                MethodDescriptor methodDesc, DependencyChecker checker, DependencyNode[] parameters,
                DependencyNode result, DefaultCallGraphNode caller, InstructionLocation location,
                ExceptionConsumer exceptionConsumer) {
            this.node = node;
            this.filterClass = filterClass;
            this.methodDesc = methodDesc;
            this.checker = checker;
            this.parameters = parameters;
            this.result = result;
            this.caller = caller;
            this.location = location;
            this.exceptionConsumer = exceptionConsumer;
        }

        @Override
        public void consume(DependencyType type) {
            String className = type.getName();
            if (DependencyChecker.shouldLog) {
                System.out.println("Virtual call of " + methodDesc + " detected on " + node.getTag() + ". " +
                        "Target class is " + className);
            }
            if (className.startsWith("[")) {
                className = "java.lang.Object";
            }
            if (!isAssignableFrom(checker.getClassSource(), filterClass.getName(), className)) {
                return;
            }
            MethodReference methodRef = new MethodReference(className, methodDesc);
            final MethodDependency methodDep = checker.linkMethod(methodRef,
                    new CallLocation(caller.getMethod(), location));
            if (!methodDep.isMissing() && knownMethods.add(methodRef)) {
                methodDep.use();
                DependencyNode[] targetParams = methodDep.getVariables();
                if (parameters[0] != null && targetParams[0] != null) {
                    parameters[0].connect(targetParams[0], new DependencyTypeFilter() {
                        @Override public boolean match(DependencyType thisType) {
                            return isAssignableFrom(checker.getClassSource(), methodDep.getMethod().getOwnerName(),
                                    thisType.getName());
                        }
                    });
                }
                for (int i = 1; i < parameters.length; ++i) {
                    if (parameters[i] != null && targetParams[i] != null) {
                        parameters[i].connect(targetParams[i]);
                    }
                }
                if (result != null && methodDep.getResult() != null) {
                    methodDep.getResult().connect(result);
                }
                methodDep.getThrown().addConsumer(exceptionConsumer);
            }
        }
    }

    private static boolean isAssignableFrom(ClassReaderSource classSource, String supertype, String subtypeName) {
        if (supertype.equals(subtypeName)) {
            return true;
        }
        ClassReader subtype = classSource.get(subtypeName);
        if (subtype == null) {
            return false;
        }
        if (subtype.getParent() != null && isAssignableFrom(classSource, supertype, subtype.getParent())) {
            return true;
        }
        for (String iface : subtype.getInterfaces()) {
            if (isAssignableFrom(classSource, supertype, iface)) {
                return true;
            }
        }
        return false;
    }

    private InstructionReader reader = new InstructionReader() {
        @Override
        public void location(InstructionLocation location) {
            currentLocation = location;
        }

        @Override
        public void nop() {
        }

        @Override
        public void classConstant(VariableReader receiver, ValueType cst) {
            DependencyNode node = nodes[receiver.getIndex()];
            if (node != null) {
                node.propagate(dependencyChecker.getType("java.lang.Class"));
            }
            while (cst instanceof ValueType.Array) {
                cst = ((ValueType.Array)cst).getItemType();
            }
            if (cst instanceof ValueType.Object) {
                final String className = ((ValueType.Object)cst).getClassName();
                dependencyChecker.linkClass(className, new CallLocation(caller.getMethod(), currentLocation));
            }
        }

        @Override
        public void nullConstant(VariableReader receiver) {
        }

        @Override
        public void integerConstant(VariableReader receiver, int cst) {
        }

        @Override
        public void longConstant(VariableReader receiver, long cst) {
        }

        @Override
        public void floatConstant(VariableReader receiver, float cst) {
        }

        @Override
        public void doubleConstant(VariableReader receiver, double cst) {
        }

        @Override
        public void stringConstant(VariableReader receiver, String cst) {
            DependencyNode node = nodes[receiver.getIndex()];
            if (node != null) {
                node.propagate(dependencyChecker.getType("java.lang.String"));
            }
            MethodDependency method = dependencyChecker.linkMethod(new MethodReference(String.class,
                    "", char[].class, void.class), new CallLocation(caller.getMethod(), currentLocation));
            method.use();
        }

        @Override
        public void binary(BinaryOperation op, VariableReader receiver, VariableReader first, VariableReader second,
                NumericOperandType type) {
        }

        @Override
        public void negate(VariableReader receiver, VariableReader operand, NumericOperandType type) {
        }

        @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) {
            DependencyNode valueNode = nodes[value.getIndex()];
            DependencyNode receiverNode = nodes[receiver.getIndex()];
            if (targetType instanceof ValueType.Object) {
                String targetClsName = ((ValueType.Object)targetType).getClassName();
                final ClassReader targetClass = dependencyChecker.getClassSource().get(targetClsName);
                if (targetClass != null) {
                    if (valueNode != null && receiverNode != null) {
                        valueNode.connect(receiverNode, new DependencyTypeFilter() {
                            @Override public boolean match(DependencyType type) {
                                if (targetClass.getName().equals("java.lang.Object")) {
                                    return true;
                                }
                                return isAssignableFrom(dependencyChecker.getClassSource(), targetClass.getName(),
                                        type.getName());
                            }
                        });
                    }
                    return;
                }
            }
            if (valueNode != null && receiverNode != null) {
                valueNode.connect(receiverNode);
            }
        }

        @Override
        public void cast(VariableReader receiver, VariableReader value, NumericOperandType sourceType,
                NumericOperandType targetType) {
        }

        @Override
        public void cast(VariableReader receiver, VariableReader value, IntegerSubtype type,
                CastIntegerDirection targetType) {
        }

        @Override
        public void jumpIf(BranchingCondition cond, VariableReader operand, BasicBlockReader consequent,
                BasicBlockReader alternative) {
        }

        @Override
        public void jumpIf(BinaryBranchingCondition cond, VariableReader first, VariableReader second,
                BasicBlockReader consequent, BasicBlockReader alternative) {
        }

        @Override
        public void jump(BasicBlockReader target) {
        }

        @Override
        public void choose(VariableReader condition, List table,
                BasicBlockReader defaultTarget) {
        }

        @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 createArray(VariableReader receiver, ValueType itemType, VariableReader size) {
            DependencyNode node = nodes[receiver.getIndex()];
            if (node != null) {
                node.propagate(dependencyChecker.getType("[" + itemType));
            }
            String className = extractClassName(itemType);
            if (className != null) {
                dependencyChecker.linkClass(className, new CallLocation(caller.getMethod(), currentLocation));
            }
        }

        private String extractClassName(ValueType itemType) {
            while (itemType instanceof ValueType.Array) {
                itemType = ((ValueType.Array)itemType).getItemType();
            }
            return itemType instanceof ValueType.Object ? ((ValueType.Object)itemType).getClassName() : null;
        }

        @Override
        public void createArray(VariableReader receiver, ValueType itemType,
                List dimensions) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < dimensions.size(); ++i) {
                sb.append('[');
                itemType = ((ValueType.Array)itemType).getItemType();
            }
            String itemTypeStr;
            if (itemType instanceof ValueType.Object) {
                itemTypeStr = ((ValueType.Object)itemType).getClassName();
            } else {
                itemTypeStr = itemType.toString();
            }
            sb.append(itemTypeStr);
            DependencyNode node = nodes[receiver.getIndex()];
            for (int i = 0; i < dimensions.size(); ++i) {
                if (node == null) {
                    break;
                }
                node.propagate(dependencyChecker.getType(sb.substring(i, sb.length())));
                node = node.getArrayItem();
            }
            String className = extractClassName(itemType);
            if (className != null) {
                dependencyChecker.linkClass(className, new CallLocation(caller.getMethod(), currentLocation));
            }
        }

        @Override
        public void create(VariableReader receiver, String type) {
            dependencyChecker.linkClass(type, new CallLocation(caller.getMethod(), currentLocation));
            DependencyNode node = nodes[receiver.getIndex()];
            if (node != null) {
                node.propagate(dependencyChecker.getType(type));
            }
        }

        @Override
        public void getField(VariableReader receiver, VariableReader instance, FieldReference field,
                ValueType fieldType) {
            FieldDependency fieldDep = dependencyChecker.linkField(field,
                    new CallLocation(caller.getMethod(), currentLocation));
            if (!(fieldType instanceof ValueType.Primitive)) {
                DependencyNode receiverNode = nodes[receiver.getIndex()];
                if (receiverNode != null) {
                    fieldDep.getValue().connect(receiverNode);
                }
            }
            initClass(field.getClassName());
        }

        @Override
        public void putField(VariableReader instance, FieldReference field, VariableReader value,
                ValueType fieldType) {
            FieldDependency fieldDep = dependencyChecker.linkField(field,
                    new CallLocation(caller.getMethod(), currentLocation));
            if (!(fieldType instanceof ValueType.Primitive)) {
                DependencyNode valueNode = nodes[value.getIndex()];
                if (valueNode != null) {
                    valueNode.connect(fieldDep.getValue());
                }
            }
            initClass(field.getClassName());
        }

        @Override
        public void arrayLength(VariableReader receiver, VariableReader array) {
        }

        @Override
        public void cloneArray(VariableReader receiver, VariableReader array) {
            DependencyNode arrayNode = nodes[array.getIndex()];
            final DependencyNode receiverNode = nodes[receiver.getIndex()];
            if (arrayNode != null && receiverNode != null) {
                arrayNode.addConsumer(new DependencyConsumer() {
                    @Override public void consume(DependencyType type) {
                        receiverNode.propagate(type);
                    }
                });
                arrayNode.getArrayItem().connect(receiverNode.getArrayItem());
            }
        }

        @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 getElement(VariableReader receiver, VariableReader array, VariableReader index) {
            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) {
            DependencyNode valueNode = nodes[value.getIndex()];
            DependencyNode arrayNode = nodes[array.getIndex()];
            if (valueNode != null && arrayNode != null && valueNode != arrayNode.getArrayItem()) {
                valueNode.connect(arrayNode.getArrayItem());
            }
        }

        @Override
        public void invoke(VariableReader receiver, VariableReader instance, MethodReference method,
                List arguments, InvocationType type) {
            if (instance == null) {
                invokeSpecial(receiver, null, method, arguments);
            } else {
                switch (type) {
                    case SPECIAL:
                        invokeSpecial(receiver, instance, method, arguments);
                        break;
                    case VIRTUAL:
                        invokeVirtual(receiver, instance, method, arguments);
                        break;
                }
            }
        }

        private void invokeSpecial(VariableReader receiver, VariableReader instance, MethodReference method,
                List arguments) {
            CallLocation callLocation = new CallLocation(caller.getMethod(), currentLocation);
            dependencyChecker.linkClass(method.getClassName(), callLocation).initClass(callLocation);
            MethodDependency methodDep = dependencyChecker.linkMethod(method, callLocation);
            if (methodDep.isMissing()) {
                return;
            }
            methodDep.use();
            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());
        }

        private void invokeVirtual(VariableReader receiver, VariableReader instance, MethodReference method,
                List arguments) {
            MethodDependency methodDep = dependencyChecker.linkMethod(method,
                    new CallLocation(caller.getMethod(), currentLocation));
            if (methodDep.isMissing()) {
                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] = nodes[instance.getIndex()];
            DependencyConsumer listener = new VirtualCallConsumer(nodes[instance.getIndex()],
                    dependencyChecker.getClassSource().get(methodDep.getMethod().getOwnerName()),
                    method.getDescriptor(), dependencyChecker, actualArgs,
                    receiver != null ? nodes[receiver.getIndex()] : null, caller, currentLocation,
                    currentExceptionConsumer);
            nodes[instance.getIndex()].addConsumer(listener);
        }

        @Override
        public void isInstance(VariableReader receiver, VariableReader value, final ValueType type) {
            String className = extractClassName(type);
            if (className != null) {
                dependencyChecker.linkClass(className, new CallLocation(caller.getMethod(), currentLocation));
            }
        }

        @Override
        public void initClass(final String className) {
            CallLocation callLocation = new CallLocation(caller.getMethod(), currentLocation);
            dependencyChecker.linkClass(className, callLocation).initClass(callLocation);
        }

        @Override
        public void nullCheck(VariableReader receiver, VariableReader value) {
            DependencyNode valueNode = nodes[value.getIndex()];
            DependencyNode receiverNode = nodes[receiver.getIndex()];
            valueNode.connect(receiverNode);
            dependencyChecker.linkMethod(new MethodReference(NullPointerException.class, "", void.class),
                    new CallLocation(caller.getMethod(), currentLocation)).use();
            currentExceptionConsumer.consume(dependencyChecker.getType("java.lang.NullPointerException"));
        }

        @Override
        public void monitorEnter(VariableReader objectRef) {
             MethodDependency methodDep = dependencyChecker.linkMethod(
                        new MethodReference(Object.class, "monitorEnter", Object.class, void.class), null);
             nodes[objectRef.getIndex()].connect(methodDep.getVariable(1));
             methodDep.use();

             methodDep = dependencyChecker.linkMethod(
                     new MethodReference(Object.class, "monitorEnterSync", Object.class, void.class), null);
             nodes[objectRef.getIndex()].connect(methodDep.getVariable(1));
             methodDep.use();
        }

        @Override
        public void monitorExit(VariableReader objectRef) {
            MethodDependency methodDep = dependencyChecker.linkMethod(
                    new MethodReference(Object.class, "monitorExit", Object.class, void.class), null);
            nodes[objectRef.getIndex()].connect(methodDep.getVariable(1));
            methodDep.use();

            methodDep = dependencyChecker.linkMethod(
                    new MethodReference(Object.class, "monitorExitSync", Object.class, void.class), null);
            nodes[objectRef.getIndex()].connect(methodDep.getVariable(1));
            methodDep.use();
        }
    };
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy