/* * This file is part of the X10 project (http://x10-lang.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.opensource.org/licenses/eclipse-1.0.php * * This file was originally derived from the Polyglot extensible compiler framework. * * (C) Copyright 2000-2007 Polyglot project group, Cornell University * (C) Copyright IBM Corporation 2007-2012. */ package polyglot.visit; import java.util.*; import polyglot.ast.*; import polyglot.main.Reporter; import polyglot.types.*; import polyglot.util.*; import x10.ast.Async; /** * Class used to construct a CFG. */ public class CFGBuilder implements Cloneable { /** The flowgraph under construction. */ protected FlowGraph graph; /** The type system. */ protected TypeSystem ts; /** The reporter */ protected Reporter reporter; /** * The outer CFGBuilder. We create a new inner CFGBuilder when entering a * loop or try-block and when entering a finally block. */ protected CFGBuilder outer; /** * The innermost loop or try-block in lexical scope. We maintain a stack * of loops and try-blocks in order to add edges for break and continue * statements and for exception throws. When such a jump is encountered we * traverse the stack, searching for the target of the jump. */ protected Stmt innermostTarget; /** * List of terms on the path to the innermost finally block. If we are * constructing a CFG for a finally block, this is the sequence of terms * that caused entry into this and lexically enclosing finally blocks. * We construct a unique subgraph for each such path. The list * is empty if this CFGBuilder is not constructing the CFG for a finally * block. */ protected List<Term> path_to_finally; /** The data flow analysis for which we are constructing the graph. */ protected DataFlow df; /** * True if we should skip the catch blocks for the innermost try when * building edges for an exception throw. */ protected boolean skipInnermostCatches; /** * True if we should add edges for uncaught Errors to the exit node of the * graph. By default, we do not, but subclasses can change this behavior * if needed. */ protected boolean errorEdgesToExitNode; public CFGBuilder(TypeSystem ts, FlowGraph graph, DataFlow df) { this.ts = ts; this.reporter = ts.extensionInfo().getOptions().reporter; this.graph = graph; this.df = df; this.path_to_finally = Collections.<Term>emptyList(); this.outer = null; this.innermostTarget = null; this.skipInnermostCatches = false; this.errorEdgesToExitNode = false; } public FlowGraph graph() { return graph; } public DataFlow dataflow() { return df; } public CFGBuilder outer() { return outer; } /** Get the type system. */ public TypeSystem typeSystem() { return ts; } /** Copy the CFGBuilder. */ private CFGBuilder shallowCopy() { try { return (CFGBuilder) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalCompilerError("Java clone() weirdness."); } } /** * Construct a new CFGBuilder with the a new innermost loop or * try-block <code>n</code>. */ public CFGBuilder push(Stmt n) { return push(n, false); } /** * Construct a new CFGBuilder with the a new innermost loop or * try-block <code>n</code>, optionally skipping innermost catch blocks. */ public CFGBuilder push(Stmt n, boolean skipInnermostCatches) { CFGBuilder v = shallowCopy(); v.outer = this; v.innermostTarget = n; v.skipInnermostCatches = skipInnermostCatches; return v; } /** * Visit edges from a branch. Simulate breaking/continuing out of * the loop, visiting any finally blocks encountered. */ public void visitBranchTarget(Branch b) { Term last = b; CFGBuilder last_visitor = this; for (CFGBuilder v = this; v != null; v = v.outer) { Term c = v.innermostTarget; if (c instanceof Async) { throw new CFGBuildError("Cannot "+b.kind()+" in an async", b.position()); } if (c instanceof Try) { Try tr = (Try) c; if (tr.finallyBlock() != null) { last_visitor = tryFinally(v, last, last_visitor, tr.finallyBlock()); last = tr.finallyBlock(); } } if (b.labelNode() != null) { if (c instanceof Labeled) { Labeled l = (Labeled) c; if (l.labelNode().id().equals(b.labelNode().id())) { if (b.kind() == Branch.BREAK) { edge(last_visitor, last, l, Term.EXIT, FlowGraph.EDGE_KEY_OTHER); } else { Stmt s = l.statement(); if (s instanceof Loop) { Loop loop = (Loop) s; edge(last_visitor, last, loop.continueTarget(), Term.ENTRY, FlowGraph.EDGE_KEY_OTHER); } else { throw new CFGBuildError("Target of continue or break statement must be a loop.", l.position()); } } return; } } } else { if (c instanceof Loop) { Loop l = (Loop) c; if (b.kind() == Branch.CONTINUE) { edge(last_visitor, last, l.continueTarget(), Term.ENTRY, FlowGraph.EDGE_KEY_OTHER); } else { edge(last_visitor, last, l, Term.EXIT, FlowGraph.EDGE_KEY_OTHER); } return; } else if (c instanceof Switch && b.kind() == Branch.BREAK) { edge(last_visitor, last, c, Term.EXIT, FlowGraph.EDGE_KEY_OTHER); return; } } } throw new CFGBuildError("Target of branch statement not found.", b.position()); } /** * Visit edges for a return statement. Simulate the return, visiting any * finally blocks encountered. */ public void visitReturn(Return r) { Term last = r; CFGBuilder last_visitor = this; for (CFGBuilder v = this; v != null; v = v.outer) { Term c = v.innermostTarget; if (c instanceof Try) { Try tr = (Try) c; if (tr.finallyBlock() != null) { last_visitor = tryFinally(v, last, last_visitor, tr.finallyBlock()); last = tr.finallyBlock(); } } } // Add an edge to the exit node. edge(last_visitor, last, graph.root(), Term.EXIT, FlowGraph.EDGE_KEY_OTHER); } protected static int counter = 0; /** Visit the AST, constructing the CFG. */ public void visitGraph() { String name = StringUtil.getShortNameComponent(df.getClass().getName()); name += counter++; if (reporter.should_report(Reporter.cfg, 2)) { String rootName = ""; if (graph.root() instanceof CodeNode) { CodeNode cd = (CodeNode)graph.root(); rootName = cd.codeDef().toString(); if (cd.codeDef() instanceof MemberDef) { rootName += " in " + ((MemberDef) cd.codeDef()).container().toString(); } } reporter.report(2, "digraph CFGBuild" + name + " {"); reporter.report(2, " label=\"CFGBuilder: " + name + "\\n" + rootName + "\"; fontsize=20; center=true; ratio=auto; size = \"8.5,11\";"); } // create peers for the entry and exit nodes. graph.peer(graph.root(), Collections.<Term>emptyList(), Term.ENTRY); graph.peer(graph.root(), Collections.<Term>emptyList(), Term.EXIT); this.visitCFG(graph.root(), Collections.<EdgeKeyTermPair>emptyList()); if (reporter.should_report(Reporter.cfg, 2)) reporter.report(2, "}"); } /** * Utility function to visit all edges in a list. * * If <code>entry</code> is Term.ENTRY, the final successor is * <code>after</code>'s entry node; if it's Term.EXIT, it's * <code>after</code>'s exit. */ public void visitCFGList(List<? extends Term> elements, Term after, int entry) { Term prev = null; for (Term c : elements) { if (prev != null) { visitCFG(prev, c, Term.ENTRY); } prev = c; } if (prev != null) { visitCFG(prev, after, entry); } } /** * Create an edge for a node <code>a</code> with a single successor * <code>succ</code>. * * The EdgeKey used for the edge from <code>a</code> to <code>succ</code> * will be FlowGraph.EDGE_KEY_OTHER. * * If <code>entry</code> is Term.ENTRY, the successor is <code>succ</code>'s * entry node; if it's Term.EXIT, it's <code>succ</code>'s exit. */ public void visitCFG(Term a, Term succ, int entry) { visitCFG(a, FlowGraph.EDGE_KEY_OTHER, succ, entry); } /** * Create an edge for a node <code>a</code> with a single successor * <code>succ</code>, and EdgeKey <code>edgeKey</code>. * * If <code>entry</code> is Term.ENTRY, the successor is <code>succ</code>'s * entry node; if it's Term.EXIT, it's <code>succ</code>'s exit. */ public void visitCFG(Term a, FlowGraph.EdgeKey edgeKey, Term succ, int entry) { visitCFG(a, CollectionUtil.list(new EdgeKeyTermPair(edgeKey, succ, entry))); } /** * Create edges from node <code>a</code> to successors <code>succ1</code> * and <code>succ2</code> with EdgeKeys <code>edgeKey1</code> and * <code>edgeKey2</code> respecitvely. * * <code>entry1</code> and <code>entry2</code> determine whether the * successors are entry or exit nodes. They can be Term.ENTRY or Term.EXIT. */ public void visitCFG(Term a, FlowGraph.EdgeKey edgeKey1, Term succ1, int entry1, FlowGraph.EdgeKey edgeKey2, Term succ2, int entry2) { visitCFG(a, CollectionUtil.list(new EdgeKeyTermPair(edgeKey1, succ1, entry1), new EdgeKeyTermPair(edgeKey2, succ2, entry2))); } /** * Create edges from node <code>a</code> to all successors <code>succ</code> * with the EdgeKey <code>edgeKey</code> for all edges created. * * If <code>entry</code> is Term.ENTRY, all terms in <code>succ</code> are * treated as entry nodes; if it's Term.EXIT, they are treated as exit * nodes. */ public void visitCFG(Term a, FlowGraph.EdgeKey edgeKey, List<Term> succ, int entry) { List<EdgeKeyTermPair> l = new ArrayList<EdgeKeyTermPair>(succ.size()); for (Term t : succ) { l.add(new EdgeKeyTermPair(edgeKey, t, entry)); } visitCFG(a, l); } /** * Create edges from node <code>a</code> to all successors * <code>succ</code> with the EdgeKey <code>edgeKey</code> for all edges * created. * * The <code>entry</code> list must have the same size as * <code>succ</code>, and each corresponding element determines whether a * successor is an entry or exit node (using Term.ENTRY or Term.EXIT). */ public void visitCFG(Term a, FlowGraph.EdgeKey edgeKey, List<Term> succ, List<Integer> entry) { if (succ.size() != entry.size()) { throw new IllegalArgumentException(); } List<EdgeKeyTermPair> l = new ArrayList<EdgeKeyTermPair>(succ.size()); for (int i = 0; i < succ.size(); i++) { Term t = succ.get(i); l.add(new EdgeKeyTermPair(edgeKey, t, entry.get(i))); } visitCFG(a, l); } protected static class EdgeKeyTermPair { public final FlowGraph.EdgeKey edgeKey; public final Term term; public final int entry; public EdgeKeyTermPair(FlowGraph.EdgeKey edgeKey, Term term, int entry) { this.edgeKey = edgeKey; this.term = term; this.entry = entry; } public String toString() { return "{edgeKey=" + edgeKey + ",term=" + term + "," + (entry == Term.ENTRY ? "entry" : "exit") + "}"; } } /** * Create edges for a node <code>a</code> with successors * <code>succs</code>. * @param a the source node for the edges. * @param succs a list of <code>EdgeKeyTermPair</code>s */ protected void visitCFG(Term a, List<EdgeKeyTermPair> succs) { Term child = a.firstChild(); if (child == null) { edge(this, a, Term.ENTRY, a, Term.EXIT, FlowGraph.EDGE_KEY_OTHER); } else { edge(this, a, Term.ENTRY, child, Term.ENTRY, FlowGraph.EDGE_KEY_OTHER); } if (reporter.should_report(Reporter.cfg, 2)) reporter.report(2, "// node " + a + " -> " + succs); succs = a.acceptCFG(this, succs); for (EdgeKeyTermPair s : succs) { edge(a, s.term, s.entry, s.edgeKey); } visitThrow(a); } public void visitThrow(Term a) { for (Type type : a.del().throwTypes(ts)) { visitThrow(a, Term.EXIT, type); } // Every statement can throw an error. // This is probably too inefficient. if ((a instanceof Stmt && ! (a instanceof CompoundStmt)) || (a instanceof Block && ((Block) a).statements().isEmpty())) { visitThrow(a, Term.EXIT, ts.Error()); } } /** * Create edges for an exception thrown from term <code>t</code>. */ public void visitThrow(Term t, int entry, Type type) { Term last = t; CFGBuilder last_visitor = this; Context context = ts.emptyContext(); for (CFGBuilder v = this; v != null; v = v.outer) { Term c = v.innermostTarget; if (c instanceof Try) { Try tr = (Try) c; if (! v.skipInnermostCatches) { boolean definiteCatch = false; for (Catch cb : tr.catchBlocks()) { int e = (last == t && entry == Term.ENTRY) ? Term.ENTRY : Term.EXIT; // definite catch if (type.isImplicitCastValid(cb.catchType(), context)) { edge(last_visitor, last, e, cb, Term.ENTRY, new FlowGraph.ExceptionEdgeKey(type)); definiteCatch = true; } // possible catch else if (cb.catchType().isImplicitCastValid(type, context)) { edge(last_visitor, last, e, cb, Term.ENTRY, new FlowGraph.ExceptionEdgeKey(cb.catchType())); } } if (definiteCatch) { // the exception has definitely been caught. // we can stop recursing to outer try-catch blocks return; } } if (tr.finallyBlock() != null) { last_visitor = tryFinally(v, last, last_visitor, tr.finallyBlock()); last = tr.finallyBlock(); } } } int e = (last == t && entry == Term.ENTRY) ? Term.ENTRY : Term.EXIT; // If not caught, insert a node from the thrower to exit. if (errorEdgesToExitNode || !type.isSubtype(ts.Error(), context)) { edge(last_visitor, last, e, graph.root(), Term.EXIT, new FlowGraph.ExceptionEdgeKey(type)); } } /** * Create edges for the finally block of a try-finally construct. * @param v v.innermostTarget is the Try term that the finallyBlock is assoicated with. @@@XXX * @param last the last term visited before the finally block is entered. * @param last_visitor @@@XXX * @param finallyBlock the finally block associated with a try finally block. */ protected static CFGBuilder tryFinally(CFGBuilder v, Term last, CFGBuilder last_visitor, Block finallyBlock) { //###@@@ I think that we may be using the wrong visitor to perform the // enterFinally on; should it maybe be last_visitor? we want to make // sure that the path_to_finally list grows correctly. CFGBuilder v_ = v.outer.enterFinally(last); // @@@XXX v_.edge(last_visitor, last, finallyBlock, Term.ENTRY, FlowGraph.EDGE_KEY_OTHER); // visit the finally block. v_.visitCFG(finallyBlock, Collections.<EdgeKeyTermPair>emptyList()); return v_; } /** * Enter a finally block. This method returns a new CFGBuilder * with the path_to_finally field pointing to a list that has the * Term <code>from</code> appended. */ protected CFGBuilder enterFinally(Term from) { CFGBuilder v = this.shallowCopy(); v.path_to_finally = new ArrayList<Term>(path_to_finally.size()+1); v.path_to_finally.addAll(path_to_finally); v.path_to_finally.add(from); return v; } /** * Add an edge to the CFG from the exit of <code>p</code> to either the * entry or exit of <code>q</code>. */ public void edge(Term p, Term q, int qEntry) { edge(this, p, q, qEntry, FlowGraph.EDGE_KEY_OTHER); } /** * Add an edge to the CFG from the exit of <code>p</code> to either the * entry or exit of <code>q</code>. */ public void edge(Term p, Term q, int qEntry, FlowGraph.EdgeKey edgeKey) { edge(this, p, q, qEntry, edgeKey); } /** * Add an edge to the CFG from the exit of <code>p</code> to either the * entry or exit of <code>q</code>. */ public void edge(CFGBuilder p_visitor, Term p, Term q, int qEntry, FlowGraph.EdgeKey edgeKey) { edge(p_visitor, p, Term.EXIT, q, qEntry, edgeKey); } /** * @param p_visitor The visitor used to create p ("this" is the visitor * that created q) * @param p The predecessor node in the forward graph * @param pEntry whether we are working with the entry or exit of p. Can be * Term.ENTRY or Term.EXIT. * @param q The successor node in the forward graph * @param qEntry whether we are working with the entry or exit of q. Can be * Term.ENTRY or Term.EXIT. */ public void edge(CFGBuilder p_visitor, Term p, int pEntry, Term q, int qEntry, FlowGraph.EdgeKey edgeKey) { if (reporter.should_report(Reporter.cfg, 2)) reporter.report(2, "// edge " + p + " -> " + q); FlowGraph.Peer pp = graph.peer(p, p_visitor.path_to_finally, pEntry); FlowGraph.Peer pq = graph.peer(q, path_to_finally, qEntry); if (reporter.should_report(Reporter.cfg, 3)) { // at level 3, use Peer.toString() as the label for the nodes reporter.report(2, pp.hashCode() + " [ label = \"" + StringUtil.escape(pp.toString()) + "\" ];"); reporter.report(2, pq.hashCode() + " [ label = \"" + StringUtil.escape(pq.toString()) + "\" ];"); } else if (reporter.should_report(Reporter.cfg, 2)) { // at level 2, use Node.toString() as the label for the nodes // which is more readable than Peer.toString(), but not as unique. reporter.report(2, pp.hashCode() + " [ label = \"" + StringUtil.escape(pp.node.toString()) + "\" ];"); reporter.report(2, pq.hashCode() + " [ label = \"" + StringUtil.escape(pq.node.toString()) + "\" ];"); } if (graph.forward()) { if (reporter.should_report(Reporter.cfg, 2)) { reporter.report(2, pp.hashCode() + " -> " + pq.hashCode() + " [label=\"" + edgeKey + "\"];"); } pp.succs.add(new FlowGraph.Edge(edgeKey, pq)); pq.preds.add(new FlowGraph.Edge(edgeKey, pp)); } else { if (reporter.should_report(Reporter.cfg, 2)) { reporter.report(2, pq.hashCode() + " -> " + pp.hashCode() + " [label=\"" + edgeKey + "\"];"); } pq.succs.add(new FlowGraph.Edge(edgeKey, pp)); pp.preds.add(new FlowGraph.Edge(edgeKey, pq)); } } }