Commit 9fbe8e26 authored by Javier Costa's avatar Javier Costa
Browse files

Merge branch 'summary_arcs' into develop

parents e504f19b 03d7ab11
Loading
Loading
Loading
Loading
+133 −0
Original line number Diff line number Diff line
package tfm.graphs;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.CallableDeclaration;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.InitializerDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.github.javaparser.resolution.Resolvable;
import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DirectedPseudograph;
import org.jgrapht.io.DOTExporter;

import java.util.Deque;
import java.util.LinkedList;

/**
 * A directed graph which displays the available method declarations as nodes and their
 * invocations as edges (caller to callee).
 * <br/>
 * Method declarations include both {@link ConstructorDeclaration constructors}
 * and {@link MethodDeclaration method declarations}.
 * In the future, {@link InitializerDeclaration static initializer blocks} and field initializers will be included.
 * <br/>
 * Method calls include only direct method calls, from {@link MethodCallExpr normal call},
 * to {@link ObjectCreationExpr object creation} and {@link ExplicitConstructorInvocationStmt
 * explicit constructor invokation} ({@code this()}, {@code super()}).
 */
public class CallGraph extends DirectedPseudograph<CallableDeclaration<?>, CallGraph.Edge<?>> implements Buildable<NodeList<CompilationUnit>> {
    private boolean built = false;

    public CallGraph() {
        super(null, null, false);
    }

    @Override
    public void build(NodeList<CompilationUnit> arg) {
        buildVertices(arg);
        buildEdges(arg);
        built = true;
    }

    @Override
    public boolean isBuilt() {
        return built;
    }

    protected void buildVertices(NodeList<CompilationUnit> arg) {
        arg.accept(new VoidVisitorAdapter<Void>() {
            @Override
            public void visit(MethodDeclaration n, Void arg) {
                addVertex(n);
                super.visit(n, arg);
            }

            @Override
            public void visit(ConstructorDeclaration n, Void arg) {
                addVertex(n);
                super.visit(n, arg);
            }
        }, null);
    }

    protected void buildEdges(NodeList<CompilationUnit> arg) {
        arg.accept(new VoidVisitorAdapter<Void>() {
            private final Deque<CallableDeclaration<?>> declStack = new LinkedList<>();

            // ============ Method declarations ===========
            // There are some locations not considered, which may lead to an error in the stack.
            // 1. Method calls in non-static field initializations are assigned to all constructors of that class
            // 2. Method calls in static field initializations are assigned to the static block of that class

            @Override
            public void visit(MethodDeclaration n, Void arg) {
                declStack.push(n);
                super.visit(n, arg);
                declStack.pop();
            }

            @Override
            public void visit(ConstructorDeclaration n, Void arg) {
                declStack.push(n);
                super.visit(n, arg);
                declStack.pop();
            }

            // =============== Method calls ===============
            @Override
            public void visit(MethodCallExpr n, Void arg) {
                n.resolve().toAst().ifPresent(decl -> addEdge(declStack.peek(), decl, new Edge<>(n)));
                super.visit(n, arg);
            }

            @Override
            public void visit(ObjectCreationExpr n, Void arg) {
                n.resolve().toAst().ifPresent(decl -> addEdge(declStack.peek(), decl, new Edge<>(n)));
                super.visit(n, arg);
            }

            @Override
            public void visit(ExplicitConstructorInvocationStmt n, Void arg) {
                n.resolve().toAst().ifPresent(decl -> addEdge(declStack.peek(), decl, new Edge<>(n)));
                super.visit(n, arg);
            }
        }, null);
    }

    public DOTExporter<CallableDeclaration<?>, Edge<?>> getDOTExporter() {
        int[] id = new int[]{0};
        return new DOTExporter<>(
                decl -> id[0]++ + "",
                decl -> decl.getDeclarationAsString(false, false, false),
                e -> e.getCallExpr().toString()
        );
    }

    public static class Edge<T extends Resolvable<? extends ResolvedMethodLikeDeclaration>> extends DefaultEdge {
        protected T callExpr;

        public Edge(T callExpr) {
            this.callExpr = callExpr;
        }

        public T getCallExpr() {
            return callExpr;
        }
    }
}
+7 −1
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.stmt.ExpressionStmt;
import tfm.arcs.Arc;
import tfm.arcs.pdg.ControlDependencyArc;
import tfm.arcs.pdg.DataDependencyArc;
import tfm.arcs.sdg.CallArc;
@@ -13,6 +14,7 @@ import tfm.arcs.sdg.SummaryArc;
import tfm.graphs.Buildable;
import tfm.graphs.Graph;
import tfm.graphs.cfg.CFG;
import tfm.graphs.sdg.sumarcs.AnalysisSummaryArcsBuilder;
import tfm.graphs.sdg.sumarcs.NaiveSummaryArcsBuilder;
import tfm.nodes.GraphNode;
import tfm.nodes.VariableAction;
@@ -21,7 +23,10 @@ import tfm.slicing.Slice;
import tfm.slicing.Sliceable;
import tfm.slicing.SlicingCriterion;
import tfm.utils.Context;
import tfm.utils.Logger;

import java.io.StringWriter;
import java.io.Writer;
import java.util.*;
import java.util.stream.Collectors;

@@ -48,8 +53,9 @@ public class SDG extends Graph implements Sliceable, Buildable<NodeList<Compilat
        nodeList.accept(createBuilder(), new Context());
        Set<GraphNode<?>> vertices = Set.copyOf(vertexSet());
        vertices.forEach(n -> new MethodCallReplacerVisitor(this).startVisit(n));
        new NaiveSummaryArcsBuilder(this).visit();
        compilationUnits = nodeList;
        // new NaiveSummaryArcsBuilder(this).visit();
         new AnalysisSummaryArcsBuilder(this).visit();
        built = true;
    }

+193 −0
Original line number Diff line number Diff line
package tfm.graphs.sdg.sumarcs;

import com.github.javaparser.ast.body.CallableDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import tfm.arcs.Arc;
import tfm.arcs.sdg.CallArc;
import tfm.graphs.CallGraph;
import tfm.graphs.sdg.SDG;
import tfm.nodes.ActualIONode;
import tfm.nodes.FormalIONode;
import tfm.nodes.GraphNode;
import tfm.nodes.type.NodeType;
import tfm.utils.Utils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Performs a fixed point analysis over the call graph of a given SDG
 */
public class AnalysisSummaryArcsBuilder extends SummaryArcsBuilder {

    private static class SummaryArcPair {
        FormalIONode in;
        GraphNode<?> out; // out node is either FormalIONode or METHOD_OUTPUT

        SummaryArcPair(FormalIONode in, GraphNode<?> out) {
            this.in = in; this.out = out;
        }
    }

    private final SDG sdg;
    private final CallGraph callGraph;

    protected Map<CallableDeclaration<?>, Set<SummaryArcPair>> vertexDataMap = new HashMap<>();
    protected boolean built = false;

    public AnalysisSummaryArcsBuilder(SDG sdg) {
        super(sdg);

        CallGraph callGraph = new CallGraph();
        callGraph.build(sdg.getCompilationUnits());

        this.sdg = sdg;
        this.callGraph = callGraph;
    }

    public AnalysisSummaryArcsBuilder(SDG sdg, CallGraph callGraph) {
        super(sdg);

        this.sdg = sdg;
        this.callGraph = callGraph;
    }

    public Set<SummaryArcPair> getResult(MethodDeclaration vertex) {
        return vertexDataMap.get(vertex);
    }

    @Override
    public void visit() {
        assert !built;
        List<CallableDeclaration<?>> workList = new LinkedList<>(callGraph.vertexSet());
        callGraph.vertexSet().forEach(v -> vertexDataMap.put(v, computeSummaryArcs(v)));
        while (!workList.isEmpty()) {
            List<CallableDeclaration<?>> newWorkList = new LinkedList<>();
            for (CallableDeclaration<?> vertex : workList) {
                updateVertex(vertex);
                Set<SummaryArcPair> newValue = computeSummaryArcs(vertex); // now with new arcs!!!
                if (!Objects.equals(vertexDataMap.get(vertex), newValue)) {
                    vertexDataMap.put(vertex, newValue);
                    newWorkList.addAll(callGraph.incomingEdgesOf(vertex).stream()
                            .map(callGraph::getEdgeSource).collect(Collectors.toSet()));
                }
            }
            workList = newWorkList;
        }
        vertexDataMap = Collections.unmodifiableMap(vertexDataMap);
        built = true;
    }

    protected void updateVertex(CallableDeclaration<?> declaration) {
        if (!declaration.isMethodDeclaration()) {
            return; // Parse only method declarations
        }

        Optional<GraphNode<MethodDeclaration>> optionalMethodDeclarationNode = sdg.findNodeByASTNode(declaration.asMethodDeclaration());

        if (optionalMethodDeclarationNode.isEmpty()) {
            return;
        }

        GraphNode<MethodDeclaration> methodDeclarationNode = optionalMethodDeclarationNode.get();

        // Get call arcs from this declaration
        Set<CallArc> methodCallExprNodes = sdg.incomingEdgesOf(methodDeclarationNode).stream()
                .filter(Arc::isCallArc)
                .map(Arc::asCallArc)
                .collect(Collectors.toSet());

        for (CallArc callArc : methodCallExprNodes) {
            GraphNode<?> methodCallNode = sdg.getEdgeSource(callArc);

            for (SummaryArcPair summaryArcPair : vertexDataMap.getOrDefault(declaration, Utils.emptySet())) {
                FormalIONode inFormalNode = summaryArcPair.in;
                GraphNode<?> outFormalNode = summaryArcPair.out;

                Optional<ActualIONode> optionalIn = sdg.outgoingEdgesOf(methodCallNode).stream()
                        .filter(arc -> (sdg.getEdgeTarget(arc)).getNodeType().is(NodeType.ACTUAL_IN))
                        .map(arc -> (ActualIONode) sdg.getEdgeTarget(arc))
                        .filter(actualIONode -> actualIONode.matchesFormalIO(inFormalNode))
                        .findFirst();

                Optional<? extends GraphNode<?>> optionalOut = sdg.outgoingEdgesOf(methodCallNode).stream()
                        .map(sdg::getEdgeTarget)
                        .filter(node -> node.getNodeType().is(NodeType.ACTUAL_OUT))
                        .filter(actualNode -> {
                            if (actualNode instanceof ActualIONode) {
                                return outFormalNode instanceof FormalIONode
                                    && ((ActualIONode) actualNode).matchesFormalIO((FormalIONode) outFormalNode);
                            }
                            // otherwise, actualNode must be METHOD_CALL_RETURN
                            if (actualNode.getNodeType() != NodeType.METHOD_CALL_RETURN) {
                                return false;
                            }

                            return outFormalNode.getNodeType() == NodeType.METHOD_OUTPUT;
                        })
                        .findFirst();

                if (optionalIn.isEmpty() || optionalOut.isEmpty()) {
                    continue;
                }

                sdg.addSummaryArc(optionalIn.get(), optionalOut.get());
            }
        }
    }

    protected Set<SummaryArcPair> computeSummaryArcs(CallableDeclaration<?> declaration) {
        Optional<GraphNode<MethodDeclaration>> optionalMethodDeclarationNode = sdg.findNodeByASTNode(declaration.asMethodDeclaration());

        if (optionalMethodDeclarationNode.isEmpty()) {
            return Utils.emptySet();
        }

        GraphNode<MethodDeclaration> methodDeclarationNode = optionalMethodDeclarationNode.get();

        // Get formal out nodes from declaration
        Set<GraphNode<?>> formalOutNodes = sdg.outgoingEdgesOf(methodDeclarationNode).stream()
                .filter(Arc::isControlDependencyArc)
                .map(sdg::getEdgeTarget)
                .filter(node -> node.getNodeType().is(NodeType.FORMAL_OUT))
                .collect(Collectors.toSet());

        Set<SummaryArcPair> res = new HashSet<>();

        for (GraphNode<?> formalOutNode : formalOutNodes) {
            for (FormalIONode formalInNode : findReachableFormalInNodes(formalOutNode)) {
                res.add(new SummaryArcPair(formalInNode, formalOutNode));
            }
        }

        return res;
    }

    private Set<FormalIONode> findReachableFormalInNodes(GraphNode<?> formalOutNode) {
        return this.doFindReachableFormalInNodes(formalOutNode, Utils.emptySet());
    }

    private Set<FormalIONode> doFindReachableFormalInNodes(GraphNode<?> root, Set<Long> visited) {
        visited.add(root.getId());

        Set<FormalIONode> res = Utils.emptySet();

        if (root.getNodeType().is(NodeType.FORMAL_IN)) {
            res.add((FormalIONode) root);
        } else {
            for (Arc arc : sdg.incomingEdgesOf(root)) {
                GraphNode<?> nextNode = sdg.getEdgeSource(arc);

                if (visited.contains(nextNode.getId())) {
                    continue;
                }

                if (arc.isDataDependencyArc() || arc.isControlDependencyArc() || arc.isSummaryArc()) {
                    res.addAll(this.doFindReachableFormalInNodes(nextNode, visited));
                }
            }
        }

        return res;
    }
}
+2 −2

File changed.

Contains only whitespace changes.