Commit 65be4201 authored by Carlos Galindo's avatar Carlos Galindo
Browse files

Better control dependence generation.

Includes documentation for the updated classes.
parent 3705ffac
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -9,8 +9,17 @@ package tfm.exec;
 *     <li>ACFG</li>: the <it>augmented</it> CFG; adds non-executable edges to represent unconditional
 *     jumps such as {@code break}, {@code return}, {@code switch} and much more.
 * </ul>
 * The <b>Program Dependence Graph</b> has variations in a similar fashion.
 * <ul>
 *     <li>PDG</li>: based on the CFG, it computes control and data dependence to connect the nodes.
 *     <li>APDG</li>: similar to the PDG, but based on the ACFG. The non-executable edges are ignored
 *     when computing data dependencies.
 * </ul>
 */
public class Config {
    public static final int CFG = 0, ACFG = 1;
    public static final int PDG = 0, APDG = 1;

    public static int CFG_TYPE = CFG;
    public static int PDG_TYPE = PDG;
}
+38 −60
Original line number Diff line number Diff line
@@ -2,10 +2,10 @@ package tfm.graphs;

import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.stmt.EmptyStmt;
import edg.graphlib.Arrow;
import org.jetbrains.annotations.NotNull;
import tfm.arcs.Arc;
import tfm.arcs.data.ArcData;
import tfm.arcs.pdg.ControlDependencyArc;
import tfm.arcs.pdg.DataDependencyArc;
import tfm.nodes.GraphNode;
@@ -21,7 +21,15 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * The <b>Program Dependence Graph</b> represents the statements of a method in
 * a graph, connecting statements according to their {@link ControlDependencyArc control}
 * and {@link DataDependencyArc data} relationships. You can build one manually or use
 * the {@link tfm.visitors.pdg.PDGBuilder PDGBuilder}.
 * @see tfm.exec.Config Config (for the available variations of the PDG)
 */
public class PDGGraph extends Graph {
    public static boolean isRanked = false, isSorted = false;

    private CFGGraph cfgGraph;

@@ -38,7 +46,7 @@ public class PDGGraph extends Graph {
        return "Entry";
    }

    public GraphNode addNode(GraphNode<?> node) {
    public GraphNode<?> addNode(GraphNode<?> node) {
        GraphNode<?> vertex = new GraphNode<>(node);
        super.addVertex(vertex);

@@ -58,32 +66,32 @@ public class PDGGraph extends Graph {
    }

    @SuppressWarnings("unchecked")
    private void addArc(Arc arc) {
        super.addEdge(arc);
    private void addArc(Arc<? extends ArcData> arc) {
        super.addEdge((Arrow<String, ArcData>) arc);
    }

    public void addControlDependencyArc(GraphNode from, GraphNode to) {
    public void addControlDependencyArc(GraphNode<?> from, GraphNode<?> to) {
        ControlDependencyArc controlDependencyArc = new ControlDependencyArc(from, to);

        this.addArc(controlDependencyArc);
    }

    public void addDataDependencyArc(GraphNode from, GraphNode to, String variable) {
    public void addDataDependencyArc(GraphNode<?> from, GraphNode<?> to, String variable) {
        DataDependencyArc dataDataDependencyArc = new DataDependencyArc(from, to, variable);

        this.addArc(dataDataDependencyArc);
    }

    public Set<GraphNode> getNodesAtLevel(int level) {
    public Set<GraphNode<?>> getNodesAtLevel(int level) {
        return getVerticies().stream()
                .map(vertex -> (GraphNode) vertex)
                .map(vertex -> (GraphNode<?>) vertex)
                .filter(node -> getLevelOf(node) == level)
                .collect(Collectors.toSet());
    }

    public int getLevels() {
        return getVerticies().stream()
                .map(vertex -> (GraphNode) vertex)
                .map(vertex -> (GraphNode<?>) vertex)
                .max(Comparator.comparingInt(this::getLevelOf))
                .map(node -> getLevelOf(node) + 1)
                .orElse(0);
@@ -98,6 +106,7 @@ public class PDGGraph extends Graph {
    public int getLevelOf(@NotNull GraphNode<?> node) {
        Optional<ControlDependencyArc> optionalControlDependencyArc = node.getIncomingArcs().stream()
                .filter(Arc::isControlDependencyArrow)
                .filter(a -> a.getFromNode().getId() < node.getId())
                .findFirst()
                .map(arc -> (ControlDependencyArc) arc);

@@ -127,21 +136,24 @@ public class PDGGraph extends Graph {

        // No level 0 is needed (only one node)
        for (int i = 0; i < getLevels(); i++) {
            Set<GraphNode> levelNodes = getNodesAtLevel(i);
            Set<GraphNode<?>> levelNodes = getNodesAtLevel(i);

            if (levelNodes.size() <= 1) {
                continue;
            }

            // rank same
            if (isRanked) {
                rankedNodes.append("{ rank = same; ")
                        .append(levelNodes.stream()
                                .map(node -> String.valueOf(node.getId()))
                                .collect(Collectors.joining(";")))
                        .append(" }")
                        .append(lineSep);
            }

            // invisible arrows for ordering
            if (isSorted) {
                rankedNodes.append(levelNodes.stream()
                        .sorted(Comparator.comparingInt(GraphNode::getId))
                        .map(node -> String.valueOf(node.getId()))
@@ -149,10 +161,11 @@ public class PDGGraph extends Graph {
                        .append("[style = invis];")
                        .append(lineSep);
            }
        }

        String arrows =
                getArcs().stream()
                        .sorted(Comparator.comparingInt(arrow -> ((GraphNode) arrow.getFrom()).getId()))
                        .sorted(Comparator.comparingInt(arrow -> ((GraphNode<?>) arrow.getFrom()).getId()))
                        .map(Arc::toGraphvizRepresentation)
                        .collect(Collectors.joining(lineSep));

@@ -173,22 +186,7 @@ public class PDGGraph extends Graph {
            throw new NodeNotFoundException(slicingCriterion);
        }

        GraphNode node = optionalGraphNode.get();

//        // DEPRECATED - Find CFGNode and find last definition of variable
//        CFGNode cfgNode = this.cfgGraph.findNodeByASTNode(node.getAstNode())
//                .orElseThrow(() -> new NodeNotFoundException("CFGNode not found"));
//
//        Set<CFGNode<?>> definitionNodes = Utils.findLastDefinitionsFrom(cfgNode, slicingCriterion.getVariable());
//
//        Logger.format("Slicing node: %s", node);
//
//        // Get slice nodes from definition nodes
//        Set<Integer> sliceNodes = definitionNodes.stream()
//                .flatMap(definitionNode -> getSliceNodes(new HashSet<>(), this.findNodeByASTNode(definitionNode.getAstNode()).get()).stream())
//                .collect(Collectors.toSet());
//
//        sliceNodes.add(node.getId());
        GraphNode<?> node = optionalGraphNode.get();

        // Simply get slice nodes from GraphNode
        Set<Integer> sliceNodes = getSliceNodes(new HashSet<>(), node);
@@ -199,7 +197,7 @@ public class PDGGraph extends Graph {

        astCopy.accept(new PDGBuilder(sliceGraph), sliceGraph.getRootNode());

        for (GraphNode sliceNode : sliceGraph.getNodes()) {
        for (GraphNode<?> sliceNode : sliceGraph.getNodes()) {
            if (!sliceNodes.contains(sliceNode.getId())) {
                Logger.log("Removing node " + sliceNode.getId());
                sliceNode.getAstNode().removeForced();
@@ -207,37 +205,17 @@ public class PDGGraph extends Graph {
            }
        }

//        for (Arc arc : getArcs()) {
//            Optional<GraphNode> fromOptional = sliceGraph.findNodeById(arc.getFromNode().getId());
//            Optional<GraphNode> toOptional = sliceGraph.findNodeById(arc.getToNode().getId());
//
//            if (fromOptional.isPresent() && toOptional.isPresent()) {
//                GraphNode from = fromOptional.get();
//                GraphNode to = toOptional.get();
//
//                if (arc.isControlDependencyArrow()) {
//                    sliceGraph.addControlDependencyArc(from, to);
//                } else {
//                    DataDependencyArc dataDependencyArc = (DataDependencyArc) arc;
//                    sliceGraph.addDataDependencyArc(from, to, dataDependencyArc.getData().getVariables().get(0));
//                }
//            }
//        }

        return sliceGraph;
    }

    private Set<Integer> getSliceNodes(Set<Integer> visited, GraphNode<?> root) {
        visited.add(root.getId());

        for (Arrow arrow : root.getIncomingArcs()) {
            Arc arc = (Arc) arrow;

            GraphNode<?> from = (GraphNode) arc.getFromNode();
        for (Arc<ArcData> arc : root.getIncomingArcs()) {
            GraphNode<?> from = arc.getFromNode();

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

            getSliceNodes(visited, from);
        }
+11 −2
Original line number Diff line number Diff line
@@ -14,6 +14,14 @@ import tfm.variables.VariableExtractor;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Represents a node in the various graphs ({@link tfm.graphs.CFGGraph CFG},
 * {@link tfm.graphs.PDGGraph PDG} and {@link tfm.graphs.SDGGraph SDG}),
 * including its AST representation and the connections it has to other nodes
 * in the same graph. It can hold a string of characters that will be used
 * to represent it.
 * @param <N> The type of the AST represented by this node.
 */
public class GraphNode<N extends Node> extends Vertex<String, ArcData> {

    private int id;
@@ -135,9 +143,10 @@ public class GraphNode<N extends Node> extends Vertex<String, ArcData> {
        if (!(o instanceof GraphNode))
            return false;

        GraphNode other = (GraphNode) o;
        GraphNode<?> other = (GraphNode<?>) o;

        return Objects.equals(getData(), other.getData())
        return this.getId() == other.getId()
                && Objects.equals(getData(), other.getData())
                && Objects.equals(astNode, other.astNode);
//                && Objects.equals(getIncomingArrows(), other.getIncomingArrows())
//                && Objects.equals(getOutgoingArrows(), other.getOutgoingArrows())
+88 −78
Original line number Diff line number Diff line
package tfm.visitors.pdg;

import com.github.javaparser.ast.stmt.*;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import tfm.arcs.Arc;
import tfm.arcs.data.ArcData;
import tfm.graphs.CFGGraph;
import tfm.graphs.PDGGraph;
import tfm.nodes.GraphNode;

import java.util.stream.Collectors;

public class ControlDependencyBuilder extends VoidVisitorAdapter<GraphNode> {

    private CFGGraph cfgGraph;
    private PDGGraph pdgGraph;

    public ControlDependencyBuilder(PDGGraph pdgGraph, CFGGraph cfgGraph) {
        this.pdgGraph = pdgGraph;
        this.cfgGraph = cfgGraph;
import java.util.*;

/**
 * A simple but slow finder of control dependencies.
 * <br/>
 * It has a polynomial complexity (between cubed and n**4) with respect to the number of nodes in the CFG.
 * It uses the following definition of control dependence:
 * <br/>
 * A node <i>b</i> is control dependent on another node <i>a</i> if and only if <i>b</i> postdominates
 * one but not all of the successors of <i>a</i>.
 * <br/>
 * A node <i>b</i> postdominates another node <i>a</i> if and only if <i>b</i> appears in every path
 * from <i>a</i> to the "Exit" node.
 * <br/>
 * There exist better, cheaper approaches that have linear complexity w.r.t. the number of edges in the CFG.
 * <b>Usage:</b> pass an empty {@link PDGGraph} and a filled {@link CFGGraph} and then run {@link #analyze()}.
 * This builder should only be used once, and then discarded.
 */
public class ControlDependencyBuilder {
    private final PDGGraph pdg;
    private final CFGGraph cfg;

    public ControlDependencyBuilder(PDGGraph pdg, CFGGraph cfg) {
        this.pdg = pdg;
        this.cfg = cfg;
    }

    @Override
    public void visit(ExpressionStmt expressionStmt, GraphNode parent) {
        addNodeAndControlDependency(expressionStmt, parent);
    public void analyze() {
        Map<GraphNode<?>, GraphNode<?>> nodeMap = new HashMap<>();
        nodeMap.put(cfg.getRootNode(), pdg.getRootNode());
        Set<GraphNode<?>> roots = new HashSet<>(cfg.getNodes());
        roots.remove(cfg.getRootNode());
        Set<GraphNode<?>> cfgNodes = new HashSet<>(cfg.getNodes());
        cfgNodes.removeIf(node -> node.getData().equals("Exit"));

        for (GraphNode<?> node : cfgNodes)
            registerNode(node, nodeMap);

        for (GraphNode<?> src : cfgNodes) {
            for (GraphNode<?> dest : cfgNodes) {
                if (src == dest) continue;
                if (hasControlDependence(src, dest)) {
                    pdg.addControlDependencyArc(nodeMap.get(src), nodeMap.get(dest));
                    if (pdg.contains(src))
                        roots.remove(dest);
                }

    @Override
    public void visit(IfStmt ifStmt, GraphNode parent) {
        GraphNode node = addNodeAndControlDependency(ifStmt, parent);

        ifStmt.getThenStmt().accept(this, node);

        ifStmt.getElseStmt().ifPresent(statement -> statement.accept(this, node));
            }

    @Override
    public void visit(WhileStmt whileStmt, GraphNode parent) {
        GraphNode node = addNodeAndControlDependency(whileStmt, parent);

        whileStmt.getBody().accept(this, node);
        }

    @Override
    public void visit(ForStmt forStmt, GraphNode parent) {
        String initialization = forStmt.getInitialization().stream()
                .map(com.github.javaparser.ast.Node::toString)
                .collect(Collectors.joining(","));

        String update = forStmt.getUpdate().stream()
                .map(com.github.javaparser.ast.Node::toString)
                .collect(Collectors.joining(","));

        String compare = forStmt.getCompare()
                .map(com.github.javaparser.ast.Node::toString)
                .orElse("true");


        GraphNode forNode = pdgGraph.addNode(
                String.format("for (%s;%s;%s)", initialization, compare, update),
                forStmt
        );

        pdgGraph.addControlDependencyArc(parent, forNode);

        forStmt.getBody().accept(this, forNode);
        // In the original definition, nodes were dependent by default on the Enter/Start node
        for (GraphNode<?> node : roots)
            if (!node.getData().equals("Exit"))
                pdg.addControlDependencyArc(pdg.getRootNode(), nodeMap.get(node));
    }

    @Override
    public void visit(ForEachStmt forEachStmt, GraphNode parent) {
        GraphNode node = addNodeAndControlDependency(forEachStmt, parent);

        forEachStmt.getBody().accept(this, node);
    public void registerNode(GraphNode<?> node, Map<GraphNode<?>, GraphNode<?>> nodeMap) {
        if (nodeMap.containsKey(node) || node.getData().equals("Exit"))
            return;
        GraphNode<?> clone = new GraphNode<>(node.getId(), node.getData(), node.getAstNode());
        nodeMap.put(node, clone);
        pdg.addNode(clone);
    }

    @Override
    public void visit(SwitchStmt switchStmt, GraphNode parent) {
        GraphNode node = addNodeAndControlDependency(switchStmt, parent);

        switchStmt.getEntries().accept(this, node);
    public static boolean hasControlDependence(GraphNode<?> a, GraphNode<?> b) {
        int yes = 0;
        List<Arc<ArcData>> list = a.getOutgoingArcs();
        // Nodes with less than 1 outgoing arc cannot control another node.
        if (list.size() < 2)
            return false;
        for (Arc<ArcData> arc : list) {
            GraphNode<?> successor = arc.getToNode();
            if (postdominates(successor, b))
                yes++;
        }

    @Override
    public void visit(SwitchEntryStmt switchEntryStmt, GraphNode parent) {
        GraphNode node = addNodeAndControlDependency(switchEntryStmt, parent);

        switchEntryStmt.getStatements().accept(this, node);
        int no = list.size() - yes;
        return yes > 0 && no > 0;
    }

    private GraphNode addNodeAndControlDependency(Statement statement, GraphNode parent) {
        GraphNode<?> cfgNode = cfgGraph.findNodeByASTNode(statement).get();

        GraphNode node = pdgGraph.addNode(cfgNode.getData(), cfgNode.getAstNode());
        pdgGraph.addControlDependencyArc(parent, node);
    public static boolean postdominates(GraphNode<?> a, GraphNode<?> b) {
        return postdominates(a, b, new HashSet<>());
    }

        return node;
    private static boolean postdominates(GraphNode<?> a, GraphNode<?> b, Set<GraphNode<?>> visited) {
        // Stop w/ success if a == b or a has already been visited
        if (a.equals(b) || visited.contains(a))
            return true;
        List<Arc<ArcData>> outgoing = a.getOutgoingArcs();
        // Stop w/ failure if there are no edges to traverse from a
        if (outgoing.size() == 0)
            return false;
        // Find all possible paths starting from a, if ALL find b, then true, else false
        visited.add(a);
        for (Arc<ArcData> out : outgoing) {
            if (!postdominates(out.getToNode(), b, visited))
                return false;
        }
        return true;
    }
}
+4 −4
Original line number Diff line number Diff line
@@ -14,15 +14,15 @@ import tfm.visitors.cfg.CFGBuilder;
 * <br/>
 * <b>Usage:</b>
 * <ol>
 *     <li>Create and fill a {@link CFGGraph} for the desired method.</li>
 *     <li>Create an empty {@link CFGGraph}.</li>
 *     <li>Create an empty {@link PDGGraph} (optionally passing the {@link CFGGraph} as argument).</li>
 *     <li>Create a new {@link PDGBuilder}, passing both graphs as arguments.</li>
 *     <li>Accept the builder as a visitor of the {@link MethodDeclaration} you want to analyse using
 *     {@link com.github.javaparser.ast.Node#accept(com.github.javaparser.ast.visitor.VoidVisitor, Object) Node#accept(VoidVisitor, Object)}:
 *     {@code methodDecl.accept(builder, null)}</li>
 *     <li>Once the previous step is finished, the complete PDG is saved in
 *     the object created in the second step. <emph>The builder should be discarded
 *     and not reused.</emph></li>
 *     the object created in the second step. The builder should be discarded
 *     and not reused.</li>
 * </ol>
 */
public class PDGBuilder extends VoidVisitorAdapter<GraphNode<?>> {
@@ -60,7 +60,7 @@ public class PDGBuilder extends VoidVisitorAdapter<GraphNode<?>> {

        // Build control dependency
        ControlDependencyBuilder controlDependencyBuilder = new ControlDependencyBuilder(pdgGraph, cfgGraph);
        methodBody.accept(controlDependencyBuilder, parent);
        controlDependencyBuilder.analyze();

        // Build data dependency
        DataDependencyBuilder dataDependencyBuilder = new DataDependencyBuilder(pdgGraph, cfgGraph);