Skip to content
CFGBuilder.java 11.2 KiB
Newer Older
package tfm.graphs;
Javier Costa's avatar
Javier Costa committed

Carlos Galindo's avatar
Carlos Galindo committed
import com.github.javaparser.ast.Node;
Javier Costa's avatar
Javier Costa committed
import com.github.javaparser.ast.body.MethodDeclaration;
Javier Costa's avatar
Javier Costa committed
import com.github.javaparser.ast.expr.BooleanLiteralExpr;
import com.github.javaparser.ast.expr.Expression;
Carlos Galindo's avatar
Carlos Galindo committed
import com.github.javaparser.ast.expr.SimpleName;
Javier Costa's avatar
Javier Costa committed
import com.github.javaparser.ast.stmt.*;
import com.github.javaparser.ast.visitor.VoidVisitor;
Javier Costa's avatar
Javier Costa committed
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
Javier Costa's avatar
Javier Costa committed
import tfm.nodes.GraphNode;
Javier Costa's avatar
Javier Costa committed
import tfm.utils.ASTUtils;
Javier Costa's avatar
Javier Costa committed

import java.util.*;

 * Populates a {@link CFG}, given one and an AST root node.
 * For now it only accepts {@link MethodDeclaration} as roots, as it disallows
 * multiple methods.
 * <br/>
 * <b>Usage:</b>
 * <ol>
 *     <li>Create a new {@link CFG}.</li>
 *     <li>Create a new {@link CFGBuilder}, passing the graph as argument.</li>
 *     <li>Accept the builder as a visitor of the {@link MethodDeclaration} you
 *     want to analyse using {@link Node#accept(VoidVisitor, Object)}: {@code methodDecl.accept(builder, null)}</li>
 *     <li>Once the previous step is finished, the complete CFG is saved in
 *     the object created in the first step. <emph>The builder should be discarded
 *     and not reused.</emph></li>
 * </ol>
 */
public class CFGBuilder extends VoidVisitorAdapter<Void> {
Carlos Galindo's avatar
Carlos Galindo committed
    /** Stores the CFG representing the method analyzed. */
    protected final CFG graph;
Carlos Galindo's avatar
Carlos Galindo committed
    /** Nodes that haven't yet been connected to another one.
     * The next node will be the destination, they are the source. */
    protected final List<GraphNode<?>> hangingNodes = new LinkedList<>();
Carlos Galindo's avatar
Carlos Galindo committed
    /** Stack of break statements collected in various (nestable) breakable blocks. */
    protected final Deque<List<GraphNode<BreakStmt>>> breakStack = new LinkedList<>();
Carlos Galindo's avatar
Carlos Galindo committed
    /** Stack of continue statements collected in various (nestable) continuable blocks. */
    protected final Deque<List<GraphNode<ContinueStmt>>> continueStack = new LinkedList<>();
Carlos Galindo's avatar
Carlos Galindo committed
    /** Lists of labelled break statements, mapped according to their label. */
    protected final Map<SimpleName, List<GraphNode<BreakStmt>>> breakMap = new HashMap<>();
Carlos Galindo's avatar
Carlos Galindo committed
    /** Lists of labelled continue statements, mapped according to their label. */
    protected final Map<SimpleName, List<GraphNode<ContinueStmt>>> continueMap = new HashMap<>();
Carlos Galindo's avatar
Carlos Galindo committed
    /** Return statements that should be connected to the final node, if it is created at the end of the  */
    protected final List<GraphNode<ReturnStmt>> returnList = new LinkedList<>();
Carlos Galindo's avatar
Carlos Galindo committed
    /** Stack of lists of hanging cases on switch statements */
    protected final Deque<List<GraphNode<SwitchEntryStmt>>> switchEntriesStack = new LinkedList<>();
Javier Costa's avatar
Javier Costa committed

    protected CFGBuilder(CFG graph) {
Javier Costa's avatar
Javier Costa committed
        this.graph = graph;
    }

    protected <T extends Node> GraphNode<T> connectTo(T n) {
Carlos Galindo's avatar
Carlos Galindo committed
        return connectTo(n, n.toString());
    }
Javier Costa's avatar
Javier Costa committed

    protected <T extends Node> GraphNode<T> connectTo(T n, String text) {
Carlos Galindo's avatar
Carlos Galindo committed
        GraphNode<T> dest = graph.addNode(text, n);
        connectTo(dest);
        return dest;
    }
Javier Costa's avatar
Javier Costa committed

    protected void connectTo(GraphNode<?> node) {
Carlos Galindo's avatar
Carlos Galindo committed
        for (GraphNode<?> src : hangingNodes)
            graph.addControlFlowEdge(src, node);
        clearHanging();
Carlos Galindo's avatar
Carlos Galindo committed
        hangingNodes.add(node);
Javier Costa's avatar
Javier Costa committed
    }

    protected void clearHanging() {
        hangingNodes.clear();
    }

Carlos Galindo's avatar
Carlos Galindo committed
    @Override
    public void visit(ExpressionStmt expressionStmt, Void arg) {
        connectTo(expressionStmt);
    }
Javier Costa's avatar
Javier Costa committed

    @Override
    public void visit(IfStmt ifStmt, Void arg) {
Carlos Galindo's avatar
Carlos Galindo committed
        // *if* -> {then else} -> after
        GraphNode<?> cond = connectTo(ifStmt, String.format("if (%s)", ifStmt.getCondition()));
Javier Costa's avatar
Javier Costa committed

Carlos Galindo's avatar
Carlos Galindo committed
        // if -> {*then* else} -> after
Javier Costa's avatar
Javier Costa committed
        ifStmt.getThenStmt().accept(this, arg);
Carlos Galindo's avatar
Carlos Galindo committed
        List<GraphNode<?>> hangingThenNodes = new LinkedList<>(hangingNodes);
Javier Costa's avatar
Javier Costa committed

Carlos Galindo's avatar
Carlos Galindo committed
        if (ifStmt.getElseStmt().isPresent()) {
            // if -> {then *else*} -> after
            clearHanging();
Carlos Galindo's avatar
Carlos Galindo committed
            hangingNodes.add(cond);
Javier Costa's avatar
Javier Costa committed
            ifStmt.getElseStmt().get().accept(this, arg);
Carlos Galindo's avatar
Carlos Galindo committed
            hangingNodes.addAll(hangingThenNodes);
Javier Costa's avatar
Javier Costa committed
        } else {
Carlos Galindo's avatar
Carlos Galindo committed
            // if -> {then **} -> after
            hangingNodes.add(cond);
Javier Costa's avatar
Javier Costa committed
        }
Carlos Galindo's avatar
Carlos Galindo committed
        // if -> {then else} -> *after*
Javier Costa's avatar
Javier Costa committed
    }

    @Override
Carlos Galindo's avatar
Carlos Galindo committed
    public void visit(LabeledStmt n, Void arg) {
        breakMap.put(n.getLabel(), new LinkedList<>());
        continueMap.put(n.getLabel(), new LinkedList<>());
        super.visit(n, arg);
        hangingNodes.addAll(breakMap.remove(n.getLabel()));
        // Remove the label from the continue map; the list should have been emptied
        // in the corresponding loop.
Carlos Galindo's avatar
Carlos Galindo committed
        if (!continueMap.remove(n.getLabel()).isEmpty())
            throw new IllegalStateException("Labeled loop has not cleared its list of continue statements!");
    protected void hangLabelledContinueStmts(Node loopParent) {
Carlos Galindo's avatar
Carlos Galindo committed
        if (loopParent instanceof LabeledStmt) {
            SimpleName label = ((LabeledStmt) loopParent).getLabel();
            if (continueMap.containsKey(label)) {
                List<GraphNode<ContinueStmt>> list = continueMap.get(label);
                hangingNodes.addAll(list);
                list.clear();
            }
        }
    }
Javier Costa's avatar
Javier Costa committed

Carlos Galindo's avatar
Carlos Galindo committed
    @Override
    public void visit(WhileStmt whileStmt, Void arg) {
        GraphNode<?> cond = connectTo(whileStmt, String.format("while (%s)", whileStmt.getCondition()));
        breakStack.push(new LinkedList<>());
        continueStack.push(new LinkedList<>());
Javier Costa's avatar
Javier Costa committed

Javier Costa's avatar
Javier Costa committed
        whileStmt.getBody().accept(this, arg);
Javier Costa's avatar
Javier Costa committed

Carlos Galindo's avatar
Carlos Galindo committed
        hangingNodes.addAll(continueStack.pop());
        hangLabelledContinueStmts(whileStmt.getParentNode().orElse(null));
        // Loop contains anything
        if (hangingNodes.size() != 1 || hangingNodes.get(0) != cond)
            connectTo(cond);
        hangingNodes.addAll(breakStack.pop());
Javier Costa's avatar
Javier Costa committed
    }

Javier Costa's avatar
Javier Costa committed
    @Override
    public void visit(DoStmt doStmt, Void arg) {
Carlos Galindo's avatar
Carlos Galindo committed
        breakStack.push(new LinkedList<>());
        continueStack.push(new LinkedList<>());
Javier Costa's avatar
Javier Costa committed

Carlos Galindo's avatar
Carlos Galindo committed
        GraphNode<?> cond = connectTo(doStmt, String.format("while (%s)", doStmt.getCondition()));
Javier Costa's avatar
Javier Costa committed

Carlos Galindo's avatar
Carlos Galindo committed
        doStmt.getBody().accept(this, arg);
Javier Costa's avatar
Javier Costa committed

Carlos Galindo's avatar
Carlos Galindo committed
        hangingNodes.addAll(continueStack.pop());
        hangLabelledContinueStmts(doStmt.getParentNode().orElse(null));
        // Loop contains anything
        if (hangingNodes.size() != 1 || hangingNodes.get(0) != cond)
            connectTo(cond);
        hangingNodes.addAll(breakStack.pop());
Javier Costa's avatar
Javier Costa committed
    @Override
    public void visit(ForStmt forStmt, Void arg) {
Carlos Galindo's avatar
Carlos Galindo committed
        breakStack.push(new LinkedList<>());
        continueStack.push(new LinkedList<>());

        // Initialization
        forStmt.getInitialization().forEach(this::connectTo);
Carlos Galindo's avatar
Carlos Galindo committed

        // Condition
        Expression condition = forStmt.getCompare().orElse(new BooleanLiteralExpr(true));
        GraphNode<?> cond = connectTo(forStmt, String.format("for (;%s;)", condition));

        // Body and update expressions
        forStmt.getBody().accept(this, arg);
        forStmt.getUpdate().forEach(this::connectTo);
Carlos Galindo's avatar
Carlos Galindo committed

        // Condition if body contained anything
        hangingNodes.addAll(continueStack.pop());
        hangLabelledContinueStmts(forStmt.getParentNode().orElse(null));
        if ((hangingNodes.size()) != 1 || hangingNodes.get(0) != cond)
            connectTo(cond);
        hangingNodes.addAll(breakStack.pop());
Javier Costa's avatar
Javier Costa committed
    }

Javier Costa's avatar
Javier Costa committed
    @Override
    public void visit(ForEachStmt forEachStmt, Void arg) {
Carlos Galindo's avatar
Carlos Galindo committed
        breakStack.push(new LinkedList<>());
        continueStack.push(new LinkedList<>());
Javier Costa's avatar
Javier Costa committed

Carlos Galindo's avatar
Carlos Galindo committed
        GraphNode<?> cond = connectTo(forEachStmt,
                String.format("for (%s : %s)", forEachStmt.getVariable(), forEachStmt.getIterable()));
Javier Costa's avatar
Javier Costa committed

        forEachStmt.getBody().accept(this, arg);

Carlos Galindo's avatar
Carlos Galindo committed
        hangingNodes.addAll(continueStack.pop());
        hangLabelledContinueStmts(forEachStmt.getParentNode().orElse(null));
        if (hangingNodes.size() != 1 || hangingNodes.get(0) != cond)
            connectTo(cond);
        hangingNodes.addAll(breakStack.pop());
    }
Javier Costa's avatar
Javier Costa committed

Carlos Galindo's avatar
Carlos Galindo committed
    /** Switch entry, considered part of the condition of the switch. */
    @Override
    public void visit(SwitchEntryStmt entryStmt, Void arg) {
        // Case header (prev -> case EXPR)
        GraphNode<SwitchEntryStmt> node;
        if (entryStmt.getLabel().isPresent()) {
            node = connectTo(entryStmt, "case " + entryStmt.getLabel().get());
        } else {
            node = connectTo(entryStmt, "default");
        }
        switchEntriesStack.peek().add(node);
        // Case body (case EXPR --> body)
        entryStmt.getStatements().accept(this, arg);
        // body --> next
Javier Costa's avatar
Javier Costa committed
    }

    @Override
    public void visit(SwitchStmt switchStmt, Void arg) {
Carlos Galindo's avatar
Carlos Galindo committed
        // Link previous statement to the switch's selector
        switchEntriesStack.push(new LinkedList<>());
        breakStack.push(new LinkedList<>());
        GraphNode<?> cond = connectTo(switchStmt, String.format("switch (%s)", switchStmt.getSelector()));
        // expr --> each case (fallthrough by default, so case --> case too)
        for (SwitchEntryStmt entry : switchStmt.getEntries()) {
            entry.accept(this, arg); // expr && prev case --> case --> next case
            hangingNodes.add(cond); // expr --> next case
        }
        // The next statement will be linked to:
        //		1. All break statements that broke from the switch (done with break section)
        // 		2. If the switch doesn't have a default statement, the switch's selector (already present)
        // 		3. If the last entry doesn't break, to the last statement (present already)
        // If the last case is a default case, remove the selector node from the list of nodes (see 2)
        if (ASTUtils.switchHasDefaultCase(switchStmt))
            hangingNodes.remove(cond);
        List<GraphNode<SwitchEntryStmt>> entries = switchEntriesStack.pop();
Carlos Galindo's avatar
Carlos Galindo committed
        // End block and break section
        hangingNodes.addAll(breakStack.pop());
Javier Costa's avatar
Javier Costa committed
    }

    @Override
Javier Costa's avatar
Javier Costa committed
    public void visit(BreakStmt breakStmt, Void arg) {
Carlos Galindo's avatar
Carlos Galindo committed
        GraphNode<BreakStmt> node = connectTo(breakStmt);
        if (breakStmt.getLabel().isPresent())
            breakMap.get(breakStmt.getLabel().get()).add(node);
        else
            breakStack.peek().add(node);
        clearHanging();
Javier Costa's avatar
Javier Costa committed
    }

    @Override
    public void visit(ContinueStmt continueStmt, Void arg) {
Carlos Galindo's avatar
Carlos Galindo committed
        GraphNode<ContinueStmt> node = connectTo(continueStmt);
        if (continueStmt.getLabel().isPresent())
            continueMap.get(continueStmt.getLabel().get()).add(node);
        else
            continueStack.peek().add(node);
        clearHanging();
Javier Costa's avatar
Javier Costa committed
    @Override
    public void visit(ReturnStmt returnStmt, Void arg) {
Carlos Galindo's avatar
Carlos Galindo committed
        GraphNode<ReturnStmt> node = connectTo(returnStmt);
        returnList.add(node);
        clearHanging();
Javier Costa's avatar
Javier Costa committed
    }

Javier Costa's avatar
Javier Costa committed
    @Override
    public void visit(MethodDeclaration methodDeclaration, Void arg) {
        if (graph.getRootNode().isPresent())
Javier Costa's avatar
Javier Costa committed
            throw new IllegalStateException("CFG is only allowed for one method, not multiple!");
        if (!methodDeclaration.getBody().isPresent())
            throw new IllegalStateException("The method must have a body!");

        graph.buildRootNode("ENTER " + methodDeclaration.getNameAsString(), methodDeclaration);
Javier Costa's avatar
Javier Costa committed

        hangingNodes.add(graph.getRootNode().get());
        methodDeclaration.getBody().get().accept(this, arg);
Carlos Galindo's avatar
Carlos Galindo committed
        returnList.stream().filter(node -> !hangingNodes.contains(node)).forEach(hangingNodes::add);
        connectTo(new EmptyStmt(), "Exit");
Javier Costa's avatar
Javier Costa committed
    }
}