/*
* 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.frontend.Globals;
import polyglot.frontend.Job;
import polyglot.frontend.Compiler;
import polyglot.main.Reporter;
import polyglot.types.*;
import polyglot.util.*;
import polyglot.visit.FlowGraph.*;
import x10.ExtensionInfo;
import x10.errors.Errors;
import x10.util.CollectionFactory;
/**
* Abstract dataflow Visitor, to allow simple dataflow equations to be easily
* implemented.
*/
public abstract class DataFlow extends ErrorHandlingVisitor
{
/**
* Indicates whether this dataflow is a forward analysis.
*/
protected final boolean forward;
/**
* Indicates whether the dataflow should be performed on entering a
* <code>CodeNode</code>, or on leaving a <code>CodeNode</code>.
* If dataflow is performed on entry, then the control flow graph
* will be available when visiting children of the
* <code>CodeNode</code>, via the <code>currentFlowGraph</code>
* method. If dataflow is performed on leaving, then the control
* flow graph will not be available, but nested
* <code>CodeNode</code>s will have already been processed.
*/
protected final boolean dataflowOnEntry;
/**
* A stack of <code>FlowGraphSource</code>. The flow graph is constructed
* upon entering a CodeNode AST node, and dataflow performed on that flow
* graph immediately. The flow graph is available during the visiting of
* children of the CodeNode, if subclasses want to use this information
* to update AST nodes. The stack is only maintained if
* <code>dataflowOnEntry</code> is true.
*/
protected LinkedList<FlowGraphSource> flowgraphStack;
protected boolean reportCFG_Errors = false; // only the first data-flow analysis should report CFG problems (like illegal break/continue)
protected boolean hadCFG_Error = false;
protected static class FlowGraphSource {
FlowGraphSource(FlowGraph g, CodeDecl s) {
this(g, (CodeNode) s);
}
FlowGraphSource(FlowGraph g, CodeNode s) {
flowgraph = g;
source = s;
}
private FlowGraph flowgraph;
private CodeNode source;
public FlowGraph flowGraph() { return flowgraph; }
public CodeNode source() { return source; }
}
/**
* Constructor.
*/
public DataFlow(Job job, TypeSystem ts, NodeFactory nf, boolean forward) {
this(job, ts, nf, forward, false);
}
/**
* Constructor.
*/
public DataFlow(Job job,
TypeSystem ts,
NodeFactory nf,
boolean forward,
boolean dataflowOnEntry) {
super(job, ts, nf);
this.forward = forward;
this.dataflowOnEntry = dataflowOnEntry;
if (dataflowOnEntry)
this.flowgraphStack = new LinkedList<FlowGraphSource>();
else
this.flowgraphStack = null;
}
/**
* An <code>Item</code> contains the data which flows during the dataflow
* analysis. Each
* node in the flow graph will have two items associated with it: the input
* item, and the output item, which results from calling flow with the
* input item. The input item may itself be the result of a call to the
* confluence method, if many paths flow into the same node.
*
* NOTE: the <code>equals(Item)</code> method and <code>hashCode()</code>
* method must be implemented to ensure that the dataflow algorithm works
* correctly.
*/
public static abstract class Item {
public abstract boolean equals(Object i);
public abstract int hashCode();
}
/**
* Create an initial Item for the term node. This is generally how the Item
* that will be given to the start node of a graph is created, although this
* method may also be called for other (non-start) nodes.
*
* @return a (possibly null) Item.
*/
protected abstract Item createInitialItem(FlowGraph graph, Term node, boolean entry);
/**
* Produce new <code>Item</code>s as appropriate for the
* <code>Term n</code> and the input <code>Item in</code>.
*
* @param in the Item flowing into the node. Note that if the Term n
* has many flows going into it, the Item in may be the result
* of a call to confluence(List, List, Term)
* @param graph the FlowGraph which the dataflow is operating on
* @param n the Term which this method must calculate the flow for.
* @param entry indicates whether we are looking at the entry or exit of n.
* @param edgeKeys a set of FlowGraph.EdgeKeys, being all the
* EdgeKeys of the edges leaving this node. The
* returned Map must have mappings for all objects in this set.
* @return a Map from FlowGraph.EdgeKeys to Items. The map must have
* entries for all EdgeKeys in edgeKeys.
*/
protected Map<EdgeKey, Item> flow(Item in, FlowGraph graph, Term n, boolean entry,
Set<EdgeKey> edgeKeys) {
throw new InternalCompilerError("Unimplemented: should be " +
"implemented by subclasses if " +
"needed");
}
/**
* Produce new <code>Item</code>s as appropriate for the
* <code>Term n</code> and the input <code>Item</code>s. The default
* implementation of this method is simply to call <code>confluence</code>
* for the list of inItems, and pass the result to flow(Item, FlowGraph,
* Term, Set). Subclasses may want to override this method if a finer grain
* dataflow is required. Some subclasses may wish to override this method
* to call <code>flowToBooleanFlow</code>.
*
* @param inItems all the Items flowing into the node.
* @param inItemKeys the FlowGraph.EdgeKeys for the items in the list inItems
* @param graph the FlowGraph which the dataflow is operating on
* @param n the Term which this method must calculate the flow for.
* @param entry indicates whether we are looking at the entry or exit of n.
* @param edgeKeys a set of FlowGraph.EdgeKeys, being all the
* EdgeKeys of the edges leaving this node. The
* returned Map must have mappings for all objects in this set.
* @return a Map from FlowGraph.EdgeKeys to Items. The map must have
* entries for all EdgeKeys in edgeKeys.
*/
protected Map<EdgeKey, Item> flow(List<Item> inItems, List<EdgeKey> inItemKeys,
FlowGraph graph, Term n, boolean entry, Set<EdgeKey> edgeKeys) {
Item inItem = this.safeConfluence(inItems, inItemKeys, n, entry, graph);
return this.flow(inItem, graph, n, entry, edgeKeys);
}
/**
* A utility method that simply collects together all the
* TRUE items, FALSE items, and all other items (including ExceptionEdgeKey
* items), calls <code>confluence</code> on each of these three collections
* as neccessary, and passes the results to
* flow(Item, Item, Item, FlowGraph, Term, Set). It is expected that
* this method will typically be called by subclasses overriding the
* flow(List, List, FlowGraph, Term, Set) method, due to the need for
* a finer grain dataflow analysis.
*
* @param inItems all the Items flowing into the node.
* @param inItemKeys the FlowGraph.EdgeKeys for the items in the list inItems
* @param graph the FlowGraph which the dataflow is operating on
* @param n the Term which this method must calculate the flow for.
* @param entry indicates whether we are looking at the entry or exit of n.
* @param edgeKeys a set of FlowGraph.EdgeKeys, being all the
* EdgeKeys of the edges leaving this node. The
* returned Map must have mappings for all objects in this set.
* @return a Map from FlowGraph.EdgeKeys to Items. The map must have
* entries for all EdgeKeys in edgeKeys.
*/
protected final Map<EdgeKey, Item> flowToBooleanFlow(List<Item> inItems,
List<EdgeKey> inItemKeys, FlowGraph graph, Term n, boolean entry,
Set<EdgeKey> edgeKeys)
{
List<Item> trueItems = new ArrayList<Item>();
List<EdgeKey> trueItemKeys = new ArrayList<EdgeKey>();
List<Item> falseItems = new ArrayList<Item>();
List<EdgeKey> falseItemKeys = new ArrayList<EdgeKey>();
List<Item> otherItems = new ArrayList<Item>();
List<EdgeKey> otherItemKeys = new ArrayList<EdgeKey>();
Iterator<Item> i = inItems.iterator();
Iterator<EdgeKey> j = inItemKeys.iterator();
while (i.hasNext() || j.hasNext()) {
Item item = (Item)i.next();
EdgeKey key = (EdgeKey)j.next();
if (FlowGraph.EDGE_KEY_TRUE.equals(key)) {
trueItems.add(item);
trueItemKeys.add(key);
}
else if (FlowGraph.EDGE_KEY_FALSE.equals(key)) {
falseItems.add(item);
falseItemKeys.add(key);
}
else {
otherItems.add(item);
otherItemKeys.add(key);
}
}
Item trueItem = trueItems.isEmpty() ? null : this.safeConfluence(trueItems, trueItemKeys, n, entry, graph);
Item falseItem = falseItems.isEmpty() ? null : this.safeConfluence(falseItems, falseItemKeys, n, entry, graph);
Item otherItem = otherItems.isEmpty() ? null : this.safeConfluence(otherItems, otherItemKeys, n, entry, graph);
return this.flow(trueItem, falseItem, otherItem, graph, n, entry, edgeKeys);
}
protected Map<EdgeKey, Item> flow(Item trueItem, Item falseItem, Item otherItem,
FlowGraph graph, Term n, boolean entry, Set<EdgeKey> edgeKeys) {
throw new InternalCompilerError("Unimplemented: should be " +
"implemented by subclasses if " +
"needed");
}
/**
*
* @param trueItem The item for flows coming into n for true conditions. Cannot be null.
* @param falseItem The item for flows coming into n for false conditions. Cannot be null.
* @param otherItem The item for all other flows coming into n
* @param n The boolean expression.
* @param edgeKeys The outgoing edges
* @return Map from edge keys to Items. Will return null if the binary
* operator was not one of !, &&, ||, & or |, to allow the calling
* method to determine which map to use.
*/
protected Map<EdgeKey, Item> flowBooleanConditions(Item trueItem, Item falseItem,
Item otherItem, FlowGraph graph, Expr n, Set<EdgeKey> edgeKeys) {
if (!n.type().isBoolean() || !(n instanceof Binary || n instanceof Unary)) {
throw new InternalCompilerError("This method only takes binary " +
"or unary operators of boolean type");
}
if (trueItem == null || falseItem == null) {
throw new IllegalArgumentException("The trueItem and falseItem " +
"for flowBooleanConditions must be non-null.");
}
if (n instanceof Unary) {
Unary u = (Unary)n;
if (u.operator() == Unary.NOT) {
return itemsToMap(falseItem, trueItem, otherItem, edgeKeys);
}
}
else {
Binary b = (Binary)n;
if (b.operator() == Binary.COND_AND) {
// the only true item coming into this node should be
// if the second operand was true.
return itemsToMap(trueItem, falseItem, otherItem, edgeKeys);
}
else if (b.operator() == Binary.COND_OR) {
// the only false item coming into this node should be
// if the second operand was false.
return itemsToMap(trueItem, falseItem, otherItem, edgeKeys);
}
else if (b.operator() == Binary.BIT_AND) {
// there is both a true and a false item coming into this node,
// from the second operand. However, this operator could be false
// if either the first or the second argument returned false.
Item bitANDFalse =
this.safeConfluence(trueItem, FlowGraph.EDGE_KEY_TRUE,
falseItem, FlowGraph.EDGE_KEY_FALSE,
n, false, graph);
return itemsToMap(trueItem, bitANDFalse, otherItem, edgeKeys);
}
else if (b.operator() == Binary.BIT_OR) {
// there is both a true and a false item coming into this node,
// from the second operand. However, this operator could be true
// if either the first or the second argument returned true.
Item bitORTrue =
this.safeConfluence(trueItem, FlowGraph.EDGE_KEY_TRUE,
falseItem, FlowGraph.EDGE_KEY_FALSE,
n, false, graph);
return itemsToMap(bitORTrue, falseItem, otherItem, edgeKeys);
}
}
return null;
}
/**
* The confluence operator for many flows. This method produces a single
* Item from a List of Items, for the confluence just before flow enters
* node.
*
* @param items List of <code>Item</code>s that flow into <code>node</code>.
* this method will only be called if the list has at least 2
* elements.
* @param node <code>Term</code> for which the <code>items</code> are
* flowing into.
* @param entry indicates whether we are looking at the entry or exit of
* node.
* @return a non-null Item.
*/
protected abstract Item confluence(List<Item> items, Term node, boolean entry,
FlowGraph graph);
/**
* The confluence operator for many flows. This method produces a single
* Item from a List of Items, for the confluence just before flow enters
* node.
*
* @param items List of <code>Item</code>s that flow into <code>node</code>.
* This method will only be called if the list has at least 2
* elements.
* @param itemKeys List of <code>FlowGraph.ExceptionEdgeKey</code>s for
* the edges that the corresponding <code>Item</code>s in
* <code>items</code> flowed from.
* @param node <code>Term</code> for which the <code>items</code> are
* flowing into.
* @param entry indicates whether we are looking at the entry or exit of
* node.
* @return a non-null Item.
*/
protected Item confluence(List<Item> items, List<EdgeKey> itemKeys, Term node,
boolean entry, FlowGraph graph) {
return confluence(items, node, entry, graph);
}
/**
* The confluence operator for many flows. This method produces a single
* Item from a List of Items, for the confluence just before flow enters
* node.
*
* @param items List of <code>Item</code>s that flow into <code>node</code>.
* This method will only be called if the list has at least 2
* elements.
* @param itemKeys List of <code>FlowGraph.ExceptionEdgeKey</code>s for
* the edges that the corresponding <code>Item</code>s in
* <code>items</code> flowed from.
* @param node <code>Term</code> for which the <code>items</code> are
* flowing into.
* @param entry indicates whether we are looking at the entry or exit of
* node.
* @return a non-null Item.
*/
protected Item safeConfluence(List<Item> items, List<EdgeKey> itemKeys, Term node,
boolean entry, FlowGraph graph) {
if (items.isEmpty()) {
return this.createInitialItem(graph, node, entry);
}
if (items.size() == 1) {
return (Item)items.get(0);
}
return confluence(items, itemKeys, node, entry, graph);
}
protected Item safeConfluence(Item item1, FlowGraph.EdgeKey key1,
Item item2, FlowGraph.EdgeKey key2,
Term node, boolean entry, FlowGraph graph) {
return safeConfluence(item1, key1, item2, key2, null, null, node, entry, graph);
}
protected Item safeConfluence(Item item1, FlowGraph.EdgeKey key1,
Item item2, FlowGraph.EdgeKey key2,
Item item3, FlowGraph.EdgeKey key3,
Term node, boolean entry, FlowGraph graph) {
List<Item> items = new ArrayList<Item>(3);
List<EdgeKey> itemKeys = new ArrayList<EdgeKey>(3);
if (item1 != null) {
items.add(item1);
itemKeys.add(key1);
}
if (item2 != null) {
items.add(item2);
itemKeys.add(key2);
}
if (item3 != null) {
items.add(item3);
itemKeys.add(key3);
}
return safeConfluence(items, itemKeys, node, entry, graph);
}
/**
* Check that the term n satisfies whatever properties this
* dataflow is checking for. This method is called for each term
* in a code declaration block after the dataflow for that block of code
* has been performed.
*/
protected abstract void check(FlowGraph graph, Term n, boolean entry,
Item inItem, Map<EdgeKey, Item> outItems);
/**
* Construct a flow graph for the <code>CodeNode</code> provided, and call
* <code>dataflow(FlowGraph)</code>. Is also responsible for calling
* <code>post(FlowGraph, Block)</code> after
* <code>dataflow(FlowGraph)</code> has been called, and for pushing
* the <code>FlowGraph</code> onto the stack of <code>FlowGraph</code>s if
* dataflow analysis is performed on entry to <code>CodeNode</code> nodes.
*/
public void dataflow(CodeDecl cd) {
this.dataflow((CodeNode) cd);
}
public void reportError(SemanticException msg) {
Errors.issue(job, msg);
}
public void dataflow(CodeNode cd) {
// only bother to do the flow analysis if the body is not null...
if (cd.codeBody() != null) {
// Compute the successor of each child node.
FlowGraph g = initGraph(cd, cd);
if (g != null) {
// Build the control flow graph.
CFGBuilder v = createCFGBuilder(ts, g);
x10.ExtensionInfo x10Info = (x10.ExtensionInfo) job().extensionInfo();
x10Info.stats.startTiming("DataFlow.dataflow", "DataFlow.dataflow");
try {
hadCFG_Error = false;
v.visitGraph();
}
catch (CFGBuildError e) {
hadCFG_Error = true;
if (reportCFG_Errors) reportError(new Errors.ControlFlowGraphError(e.getMessage(), e.position));
return;
}
finally {
x10Info.stats.stopTiming();
}
x10Info.stats.startTiming("DataFlow.cfg.build", "DataFlow.cfg.build");
dataflow(g);
x10Info.stats.stopTiming();
x10Info.stats.startTiming("DataFlow.post", "DataFlow.post");
post(g, cd);
x10Info.stats.stopTiming();
// push the CFG onto the stack if we are dataflowing on entry
if (dataflowOnEntry)
flowgraphStack.addFirst(new FlowGraphSource(g, cd));
}
}
}
/** A "stack frame" for recursive DFS */
static private class Frame {
private Peer peer;
private Iterator<Edge> edges;
Frame(Peer p, boolean forward) {
peer = p;
if (forward) edges = p.succs().iterator();
else edges = p.preds().iterator();
}
}
/** Returns the linked list [by_scc, scc_head] where
* by_scc is an array in which SCCs occur in topologically
* order.
* scc_head[n] where n is the first peer in an SCC is set to -1.
* scc_head[n] where n is the last peer in a (non-singleton) SCC is set
* to the index of the first peer. Otherwise it is -2. */
protected Pair<Peer[], int[]> findSCCs(FlowGraph graph) {
Collection<Peer> peers = graph.peers();
Peer[] sorted = new Peer[peers.size()];
Collection<Peer> start = graph.startPeers();
// if start == peers, making all nodes reachable,
// the problem still arises.
//System.out.println("scc: npeers = " + peers.size());
// First, topologically sort the nodes (put in postorder)
int n = 0;
LinkedList<Frame> stack = new LinkedList<Frame>();
Set<Peer> reachable = CollectionFactory.newHashSet();
for (Peer peer : start) {
if (!reachable.contains(peer)) {
reachable.add(peer);
stack.addFirst(new Frame(peer, true));
while (stack.size() != 0) {
Frame top = (Frame)stack.getFirst();
if (top.edges.hasNext()) {
Edge e = (Edge)top.edges.next();
Peer q = e.getTarget();
if (!reachable.contains(q)) {
reachable.add(q);
stack.addFirst(new Frame(q, true));
}
} else {
stack.removeFirst();
sorted[n++] = top.peer;
}
}
}
}
//System.out.println("scc: reached " + n);
// Now, walk the transposed graph picking nodes in reverse
// postorder, thus picking out one SCC at a time and
// appending it to "by_scc".
Peer[] by_scc = new Peer[n];
int[] scc_head = new int[n];
Set<Peer> visited = CollectionFactory.newHashSet();
int head = 0;
for (int i=n-1; i>=0; i--) {
if (!visited.contains(sorted[i])) {
// First, find all the nodes in the SCC
Set<Peer> SCC = CollectionFactory.newHashSet();
visited.add(sorted[i]);
stack.add(new Frame(sorted[i], false));
while (stack.size() != 0) {
Frame top = (Frame)stack.getFirst();
if (top.edges.hasNext()) {
Edge e = (Edge)top.edges.next();
Peer q = e.getTarget();
if (reachable.contains(q) && !visited.contains(q)) {
visited.add(q);
Frame f = new Frame(q, false);
stack.addFirst(f);
}
} else {
stack.removeFirst();
SCC.add(top.peer);
}
}
// Now, topologically sort the SCC (as much as possible)
// and place into by_scc[head..head+scc_size-1]
stack.add(new Frame(sorted[i], true));
Set<Peer> revisited = CollectionFactory.newHashSet();
revisited.add(sorted[i]);
int scc_size = SCC.size();
int nsorted = 0;
while (stack.size() != 0) {
Frame top = (Frame)stack.getFirst();
if (top.edges.hasNext()) {
Edge e = (Edge)top.edges.next();
Peer q = e.getTarget();
if (SCC.contains(q) && !revisited.contains(q)) {
revisited.add(q);
Frame f = new Frame(q, true);
stack.addFirst(f);
}
} else {
stack.removeFirst();
int n3 = head + scc_size - nsorted - 1;
scc_head[n3] = -2;
by_scc[n3] = top.peer;
nsorted++;
}
}
scc_head[head+scc_size-1] = head;
scc_head[head] = -1;
head = head + scc_size;
}
}
if (reporter.should_report(Reporter.dataflow, 2)) {
for (int j = 0; j < n; j++) {
switch(scc_head[j]) {
case -1: reporter.report(2, j + "[HEAD] : " + by_scc[j]); break;
case -2: reporter.report(2, j + " : " + by_scc[j]); break;
default: reporter.report(2, j + " ->"+ scc_head[j] + " : " + by_scc[j]);
}
for (Edge edge : by_scc[j].succs()) {
reporter.report(3, " successor: " + edge.getTarget());
}
}
}
Pair<Peer[], int[]> ret = new Pair<Peer[], int[]>(by_scc, scc_head);
return ret;
}
/**
* Perform the dataflow on the flowgraph provided.
*/
protected void dataflow(FlowGraph graph) {
if (reporter.should_report(Reporter.dataflow, 1)) {
reporter.report(1, "Finding strongly connected components");
}
Pair<Peer[], int[]> pair = findSCCs(graph);
Peer[] by_scc = pair.fst();
int[] scc_head = pair.snd();
int npeers = by_scc.length;
/* by_scc contains the peers grouped by SCC.
scc_head marks where the SCCs are. The SCC
begins with a -1 and ends with the index of
the beginning of the SCC.
*/
if (reporter.should_report(Reporter.dataflow, 1)) {
reporter.report(1, "Iterating dataflow equations");
}
int current = 0;
boolean change = false;
while (current < npeers) {
Peer p = by_scc[current];
if (scc_head[current] == -1) {
change = false; // just started working on a new SCC
}
// get the in items by examining the out items of all
// the predecessors of p
List<Item> inItems = new ArrayList<Item>(p.preds.size());
List<EdgeKey> inItemKeys = new ArrayList<EdgeKey>(p.preds.size());
for (Edge e : p.preds) {
Peer o = e.getTarget();
if (o.outItems != null) {
if (!o.outItems.keySet().contains(e.getKey())) {
throw new InternalCompilerError("There should have " +
"an out Item with edge key " + e.getKey() +
"; instead there were only " +
o.outItems.keySet());
}
Item it = (Item)o.outItems.get(e.getKey());
if (it != null) {
inItems.add(it);
inItemKeys.add(e.getKey());
}
}
}
// calculate the out item
Map<EdgeKey, Item> oldOutItems = p.outItems;
p.inItem = this.safeConfluence(inItems, inItemKeys, p.node,
p.entry == Term.ENTRY, graph);
p.outItems = this.flow(inItems, inItemKeys, graph, p.node,
p.entry == Term.ENTRY, p.succEdgeKeys());
if (!p.succEdgeKeys().equals(p.outItems.keySet())) {
// This check is more for developers to ensure that they
// have implemented their dataflow correctly. If performance
// is an issue, maybe we should remove this check.
throw new InternalCompilerError("The flow only defined " +
"outputs for " + p.outItems.keySet() + "; needs to " +
"define outputs for all of: " + p.succEdgeKeys());
}
if (oldOutItems != p.outItems &&
(oldOutItems == null || !oldOutItems.equals(p.outItems))) {
// the outItems of p has changed, so we will
// loop when we get to the end of the current SCC.
change = true;
}
if (change && scc_head[current] >= 0) {
current = scc_head[current]; // loop!
/* now scc_head[current] == -1 */
} else {
current++;
}
}
if (reporter.should_report(Reporter.dataflow, 1)) {
reporter.report(1, "Done.");
}
}
/**
* Initialise the <code>FlowGraph</code> to be used in the dataflow
* analysis.
*
* @return null if no dataflow analysis should be performed for this
* code declaration; otherwise, an apropriately initialized
* <code>FlowGraph.</code>
*/
protected FlowGraph initGraph(CodeNode code, Term root) {
return new FlowGraph(root, forward);
}
/**
* Initialise the <code>FlowGraph</code> to be used in the dataflow
* analysis.
*
* @return null if no dataflow analysis should be performed for this
* code declaration; otherwise, an apropriately initialized
* <code>FlowGraph.</code>
*/
protected FlowGraph initGraph(CodeDecl code, Term root) {
return initGraph((CodeNode) code, root);
}
/**
* Construct a CFGBuilder.
*
* @param ts The type system
* @param g The flow graph to that the CFGBuilder will construct.
* @return a new CFGBuilder
*/
protected CFGBuilder createCFGBuilder(TypeSystem ts, FlowGraph g) {
return new CFGBuilder(ts, g, this);
}
/**
* Overridden superclass method, to build the flow graph, perform dataflow
* analysis, and check the analysis for CodeNode nodes.
*/
protected NodeVisitor enterCall(Node n) {
if (dataflowOnEntry && n instanceof CodeNode) {
dataflow((CodeNode)n);
}
return this;
}
/**
* Overridden superclass method, to make sure that if a subclass has changed
* a Term, that we update the peermaps appropriately, since they are based
* on <code>IdentityKey</code>s.
*/
public Node leave(Node parent, Node old, Node n, NodeVisitor v) {
if (old != n) {
if (dataflowOnEntry && currentFlowGraph() != null) {
// We currently only update the key in the peerMap.
// We DO NOT update the Terms inside the peers, nor the
// List of Terms that are the path maps.
Map<PeerKey, Peer> o = currentFlowGraph().peerMap.get(new IdentityKey(old));
if (o != null) {
currentFlowGraph().peerMap.put(new IdentityKey(n), o);
}
}
}
return super.leave(parent, old, n, v);
}
/**
* Overridden superclass method, to pop from the stack of
* <code>FlowGraph</code>s if necessary.
*/
protected Node leaveCall(Node old, Node n, NodeVisitor v) {
if (n instanceof CodeNode) {
if (!dataflowOnEntry) {
dataflow((CodeNode)n);
}
else if (dataflowOnEntry && !flowgraphStack.isEmpty()) {
FlowGraphSource fgs = (FlowGraphSource)flowgraphStack.getFirst();
if (fgs.source.equals(old)) {
// we are leaving the code decl that pushed this flowgraph
// on the stack. pop tbe stack.
flowgraphStack.removeFirst();
}
}
}
return n;
}
/**
* Check all of the Peers in the graph, after the dataflow analysis has
* been performed.
*/
protected void post(FlowGraph graph, Term root) {
if (reporter.should_report(Reporter.cfg, 2)) {
dumpFlowGraph(graph, root);
}
// Check the nodes in approximately flow order.
Set<Peer> uncheckedPeers = CollectionFactory.newHashSet(graph.peers());
LinkedList<Peer> peersToCheck = new LinkedList<Peer>(graph.startPeers());
while (!peersToCheck.isEmpty()) {
Peer p = (Peer) peersToCheck.removeFirst();
uncheckedPeers.remove(p);
this.check(graph, p.node, p.entry == Term.ENTRY, p.inItem, p.outItems);
for (Edge e : p.succs) {
Peer q = e.getTarget();
if (uncheckedPeers.contains(q) && !peersToCheck.contains(q)) {
// q hasn't been checked yet.
peersToCheck.addLast(q);
}
}
if (peersToCheck.isEmpty() && !uncheckedPeers.isEmpty()) {
// done all the we can reach...
Iterator<Peer> i = uncheckedPeers.iterator();
peersToCheck.add(i.next());
i.remove();
}
}
}
/**
* Return the <code>FlowGraph</code> at the top of the stack. This method
* should not be called if dataflow is not being performed on entry to
* the <code>CodeNode</code>s, as the stack is not maintained in that case.
* If this
* method is called by a subclass from the <code>enterCall</code>
* or <code>leaveCall</code> methods, for an AST node that is a child
* of a <code>CodeNode</code>, then the <code>FlowGraph</code> returned
* should be the <code>FlowGraph</code> for the dataflow for innermost
* <code>CodeNode</code>.
*/
protected FlowGraph currentFlowGraph() {
if (!dataflowOnEntry) {
throw new InternalCompilerError("currentFlowGraph() cannot be" +
" called when dataflow is not performed on entry");
}
if (flowgraphStack.isEmpty()) {
return null;
}
return ((FlowGraphSource)flowgraphStack.getFirst()).flowgraph;
}
/**
* This utility methods is for subclasses to convert a single Item into
* a <code>Map</code>, to return from the
* <code>flow</code> methods. This
* method should be used when the same output <code>Item</code> from the
* flow is to be used for all edges leaving the node.
*
* @param i the <code>Item</code> to be placed in the returned
* <code>Map</code> as the value for every <code>EdgeKey</code> in
* <code>edgeKeys.</code>
* @param edgeKeys the <code>Set</code> of <code>EdgeKey</code>s to be used
* as keys in the returned <code>Map</code>.
* @return a <code>Map</code> containing a mapping from every
* <code>EdgeKey</code> in <code>edgeKeys</code> to the
* <code>Item i</code>.
*/
public static final Map<EdgeKey, Item> itemToMap(Item i, Set<EdgeKey> edgeKeys) {
Map<EdgeKey, Item> m = CollectionFactory.newHashMap();
for (EdgeKey o : edgeKeys) {
m.put(o, i);
}
return m;
}
/**
* This utility method is for subclasses to convert Items into
* a <code>Map</code>, to return from the
* <code>flow</code> methods.
*
* @param trueItem the <code>Item</code> to be placed in the returned
* <code>Map</code> as the value for the
* <code>FlowGraph.EDGE_KEY_TRUE</code>, if that key is present in
* <code>edgeKeys.</code>
* @param falseItem the <code>Item</code> to be placed in the returned
* <code>Map</code> as the value for the
* <code>FlowGraph.EDGE_KEY_FALSE</code>, if that key is present in
* <code>edgeKeys.</code>
* @param remainingItem the <code>Item</code> to be placed in the returned
* <code>Map</code> as the value for any edge key other than
* <code>FlowGraph.EDGE_KEY_TRUE</code> or
* <code>FlowGraph.EDGE_KEY_FALSE</code>, if any happen to be
* present in
* <code>edgeKeys.</code>
* @param edgeKeys the <code>Set</code> of <code>EdgeKey</code>s to be used
* as keys in the returned <code>Map</code>.
* @return a <code>Map</code> containing a mapping from every
* <code>EdgeKey</code> in <code>edgeKeys</code> to the
* <code>Item i</code>.
*/
protected static final Map<EdgeKey, Item>
itemsToMap(Item trueItem, Item falseItem, Item remainingItem, Set<EdgeKey> edgeKeys)
{
Map<EdgeKey, Item> m = CollectionFactory.newHashMap();
for (EdgeKey k : edgeKeys) {
if (FlowGraph.EDGE_KEY_TRUE.equals(k)) {
m.put(k, trueItem);
}
else if (FlowGraph.EDGE_KEY_FALSE.equals(k)) {
m.put(k, falseItem);
}
else {
m.put(k, remainingItem);
}
}
return m;
}
/**
* Filter a list of <code>Item</code>s to contain only <code>Item</code>s
* that are not associated with error flows, that is, only
* <code>Item</code>s whose associated <code>EdgeKey</code>s are not
* <code>FlowGraph.ExceptionEdgeKey</code>s with a type that is a subclass
* of <code>TypeSystem.Error()</code>.
*
* @param items List of Items to filter
* @param itemKeys List of <code>EdgeKey</code>s corresponding
* to the edge keys for the <code>Item</code>s in <code>items</code>.
* @return a filtered list of items, containing only those whose edge keys
* are not <code>FlowGraph.ExceptionEdgeKey</code>s with
* whose exception types are <code>Error</code>s.
*/
protected final List<Item> filterItemsNonError(List<Item> items, List<EdgeKey> itemKeys) {
List<Item> filtered = new ArrayList<Item>(items.size());
Iterator<Item> i = items.iterator();
Iterator<EdgeKey> j = itemKeys.iterator();
while (i.hasNext() && j.hasNext()) {
Item item = (Item)i.next();
EdgeKey key = (EdgeKey)j.next();
if (!(key instanceof ExceptionEdgeKey &&
((ExceptionEdgeKey)key).type().isSubtype(ts.Error(), ts.emptyContext()))) {
// the key is not an error edge key.
filtered.add(item);
}
}
if (i.hasNext() || j.hasNext()) {
throw new InternalCompilerError("item and item key lists " +
"have different sizes.");
}
return filtered;
}
/**
* Filter a list of <code>Item</code>s to contain only <code>Item</code>s
* that are not associated with exception flows, that is, only
* <code>Item</code>s whose associated <code>EdgeKey</code>s are not
* <code>FlowGraph.ExceptionEdgeKey</code>s.
*
* @param items List of Items to filter
* @param itemKeys List of <code>EdgeKey</code>s corresponding
* to the edge keys for the <code>Item</code>s in <code>items</code>.
* @return a filtered list of items, containing only those whose edge keys
* are not <code>FlowGraph.ExceptionEdgeKey</code>s.
*/
protected final List<Item> filterItemsNonException(List<Item> items, List<EdgeKey> itemKeys) {
List<Item> filtered = new ArrayList<Item>(items.size());
Iterator<Item> i = items.iterator();
Iterator<EdgeKey> j = itemKeys.iterator();
while (i.hasNext() && j.hasNext()) {
Item item = (Item)i.next();
EdgeKey key = (EdgeKey)j.next();
if (!(key instanceof ExceptionEdgeKey)) {
// the key is not an exception edge key.
filtered.add(item);
}
}
if (i.hasNext() || j.hasNext()) {
throw new InternalCompilerError("item and item key lists " +
"have different sizes.");
}
return filtered;
}
/**
* Filter a list of <code>Item</code>s to contain only <code>Item</code>s
* that are associated with exception flows, whose exception is a subclass
* of <code>excType</code>. That is, only
* <code>Item</code>s whose associated <code>EdgeKey</code>s are
* <code>FlowGraph.ExceptionEdgeKey</code>s, with the type a subclass
* of <code>excType</code>.
*
* @param items List of Items to filter
* @param itemKeys List of <code>EdgeKey</code>s corresponding
* to the edge keys for the <code>Item</code>s in <code>items</code>.
* @param excType an Exception <code>Type</code>.
* @return a filtered list of items, containing only those whose edge keys
* are not <code>FlowGraph.ExceptionEdgeKey</code>s.
*/
protected final List<Item> filterItemsExceptionSubclass(List<Item> items, List<EdgeKey> itemKeys,
Type excType) {
List<Item> filtered = new ArrayList<Item>(items.size());
Iterator<Item> i = items.iterator();
Iterator<EdgeKey> j = itemKeys.iterator();
while (i.hasNext() && j.hasNext()) {
Item item = (Item)i.next();
EdgeKey key = (EdgeKey)j.next();
if (key instanceof ExceptionEdgeKey) {
// the key is an exception edge key.
ExceptionEdgeKey eek = (ExceptionEdgeKey)key;
if (eek.type().isImplicitCastValid(excType, ts.emptyContext())) {
filtered.add(item);
}
}
}
if (i.hasNext() || j.hasNext()) {
throw new InternalCompilerError("item and item key lists " +
"have different sizes.");
}
return filtered;
}
/**
* Filter a list of <code>Item</code>s to contain only <code>Item</code>s
* that are associated with the given <code>EdgeKey</code>.
*
* @param items List of Items to filter
* @param itemKeys List of <code>EdgeKey</code>s corresponding
* to the edge keys for the <code>Item</code>s in <code>items</code>.
* @param filterEdgeKey the <code>EdgeKey</code> to use as a filter.
* @return a filtered list of items, containing only those whose edge keys
* are the same as <code>filterEdgeKey</code>s.
*/
protected final List<Item> filterItems(List<Item> items, List<EdgeKey> itemKeys,
EdgeKey filterEdgeKey) {
List<Item> filtered = new ArrayList<Item>(items.size());
Iterator<Item> i = items.iterator();
Iterator<EdgeKey> j = itemKeys.iterator();
while (i.hasNext() && j.hasNext()) {
Item item = (Item)i.next();
EdgeKey key = (EdgeKey)j.next();
if (filterEdgeKey.equals(key)) {
// the key matches the filter
filtered.add(item);
}
}
if (i.hasNext() || j.hasNext()) {
throw new InternalCompilerError("item and item key lists " +
"have different sizes.");
}
return filtered;
}
/**
* This utility method is for subclasses to determine if the node currently
* under consideration has both true and false edges leaving it. That is,
* the flow graph at this node has successor edges with the
* <code>EdgeKey</code>s <code>Edge_KEY_TRUE</code> and
* <code>Edge_KEY_FALSE</code>.
*
* @param edgeKeys the <code>Set</code> of <code>EdgeKey</code>s of the
* successor edges of a given node.
* @return true if the <code>edgeKeys</code> contains both
* <code>Edge_KEY_TRUE</code> and
* <code>Edge_KEY_FALSE</code>
*/
protected static final boolean hasTrueFalseBranches(Set<EdgeKey> edgeKeys) {
return edgeKeys.contains(FlowGraph.EDGE_KEY_FALSE) &&
edgeKeys.contains(FlowGraph.EDGE_KEY_TRUE);
}
/**
* This utility method is meant to be used by subclasses to help them
* produce appropriate <code>Item</code>s for the
* <code>FlowGraph.EDGE_KEY_TRUE</code> and
* <code>FlowGraph.EDGE_KEY_FALSE</code> edges from a boolean condition.
*
* @param booleanCond the boolean condition that is used to branch on. The
* type of the expression must be boolean.
* @param startingItem the <code>Item</code> at the start of the flow for
* the expression <code>booleanCond</code>.
* @param succEdgeKeys the set of <code>EdgeKeys</code> of the successor
* nodes of the current node. Must contain both
* <code>FlowGraph.EDGE_KEY_TRUE</code>
* and <code>FlowGraph.EDGE_KEY_FALSE</code>.
* @param navigator an instance of <code>ConditionNavigator</code> to be
* used to generate appropriate <code>Item</code>s from the
* boolean condition.
* @return a <code>Map</code> containing mappings for all entries in
* <code>succEdgeKeys</code>.
* <code>FlowGraph.EDGE_KEY_TRUE</code> and
* <code>FlowGraph.EDGE_KEY_FALSE</code>
* map to <code>Item</code>s calculated for them using
* navigator, and all other objects in
* <code>succEdgeKeys</code> are mapped to
* <code>startingItem</code>.
* @deprecated
*/
protected static Map<EdgeKey, Item> constructItemsFromCondition(Expr booleanCond,
Item startingItem,
Set<EdgeKey> succEdgeKeys,
ConditionNavigator navigator) {
// check the arguments to make sure this method is used correctly
if (!booleanCond.type().isBoolean()) {
throw new IllegalArgumentException("booleanCond must be a boolean expression");
}
if (!hasTrueFalseBranches(succEdgeKeys)) {
throw new IllegalArgumentException("succEdgeKeys does not have true and false branches.");
}
BoolItem results = navigator.navigate(booleanCond, startingItem);
Map<EdgeKey, Item> m = CollectionFactory.newHashMap();
m.put(FlowGraph.EDGE_KEY_TRUE, results.trueItem);
m.put(FlowGraph.EDGE_KEY_FALSE, results.falseItem);
// put the starting item in the map for any EdgeKeys other than
// FlowGraph.EDGE_KEY_TRUE and FlowGraph.EDGE_KEY_FALSE
for (EdgeKey e : succEdgeKeys) {
if (!FlowGraph.EDGE_KEY_TRUE.equals(e) &&
!FlowGraph.EDGE_KEY_FALSE.equals(e)) {
m.put(e, startingItem);
}
}
return m;
}
/**
* This class contains two <code>Item</code>s, one being the
* <code>Item</code> that is used when an expression is true, the
* other being the one that is used when an expression is false. It is used
* by the <code>ConditionNavigator</code>.
* @deprecated Use flowBooleanConditions
*/
protected static class BoolItem {
public BoolItem(Item trueItem, Item falseItem) {
this.trueItem = trueItem;
this.falseItem = falseItem;
}
private Item trueItem;
private Item falseItem;
public Item trueItem() { return trueItem; }
public Item falseItem() { return falseItem; }
public String toString() {
return "[ true: " + trueItem + "; false: " + falseItem + " ]";
}
}
/**
* A <code>ConditionNavigator</code> is used to traverse boolean
* expressions that are
* used as conditions, such as in if statements, while statements,
* left branches of && and ||. The <code>ConditionNavigator</code> is used
* to generate
* a finer-grained analysis, so that the branching flows from a
* condition can take into account the fact that the condition is true or
* false. For example, in the statement <code>if (cond) s1 else s2</code>,
* dataflow for <code>s1</code> can continue in the knowledge that
* <code>cond</code> evaluated to true, and similarly, <code>s2</code>
* can be analyzed using the knowledge that <code>cond</code> evaluated to
* false.
*
* @deprecated
*/
protected abstract static class ConditionNavigator {
/**
* Navigate the expression <code>expr</code>, where the
* <code>Item</code> at the start of evaluating the expression is
* <code>startingItem</code>.
*
* A <code>BoolItem</code> is returned, containing the
* <code>Item</code>s that are appropriate when <code>expr</code>
* evaluates to true and false.
*/
public BoolItem navigate(Expr expr, Item startingItem) {
if (expr.type().isBoolean()) {
if (expr instanceof Binary) {
Binary b = (Binary)expr;
if (Binary.COND_AND.equals(b.operator()) ||
Binary.BIT_AND.equals(b.operator())) {
BoolItem leftRes = navigate(b.left(), startingItem);
Item rightResStart = startingItem;
if (Binary.COND_AND.equals(b.operator())) {
// due to short circuiting, if the right
// branch is evaluated, the starting item is
// in fact the true part of the left result
rightResStart = leftRes.trueItem;
}
BoolItem rightRes = navigate(b.right(), rightResStart);
return andResults(leftRes, rightRes, startingItem);
}
else if (Binary.COND_OR.equals(b.operator()) ||
Binary.BIT_OR.equals(b.operator())) {
BoolItem leftRes = navigate(b.left(), startingItem);
Item rightResStart = startingItem;
if (Binary.COND_OR.equals(b.operator())) {
// due to short circuiting, if the right
// branch is evaluated, the starting item is
// in fact the false part of the left result
rightResStart = leftRes.falseItem;
}
BoolItem rightRes = navigate(b.right(), rightResStart);
return orResults(leftRes, rightRes, startingItem);
}
}
else if (expr instanceof Unary) {
Unary u = (Unary)expr;
if (Unary.NOT.equals(u.operator())) {
BoolItem res = navigate(u.expr(), startingItem);
return notResult(res);
}
}
}
// either we are not a boolean expression, or not a logical
// connective. Let the subclass deal with it.
return handleExpression(expr, startingItem);
}
/**
* Combine the results of analyzing the left and right arms of
* an AND boolean operator (either && or &).
*/
public BoolItem andResults(BoolItem left,
BoolItem right,
Item startingItem) {
return new BoolItem(combine(left.trueItem, right.trueItem),
startingItem);
}
/**
* Combine the results of analyzing the left and right arms of
* an OR boolean operator (either || or |).
*/
public BoolItem orResults(BoolItem left,
BoolItem right,
Item startingItem) {
return new BoolItem(startingItem,
combine(left.falseItem, right.falseItem));
}
/**
* Modify the results of analyzing the child of
* a NEGATION boolean operator (a !).
*/
public BoolItem notResult(BoolItem results) {
return new BoolItem(results.falseItem, results.trueItem);
}
/**
* Combine two <code>Item</code>s together, when the information
* contained in both items is true. Thus, for example, in a not-null
* analysis, where <code>Item</code>s are sets of not-null variables,
* combining them corresponds to unioning the sets. Note that this
* could be a different operation to the confluence operation.
*/
public abstract Item combine(Item item1, Item item2);
/**
* Produce a <code>BoolItem</code> for an expression that is not
* a boolean operator, such as &&, &, ||, | or !.
*/
public abstract BoolItem handleExpression(Expr expr, Item startingItem);
}
protected static int flowCounter = 0;
/**
* Dump a flow graph, labeling edges with their flows, to aid in the
* debugging of data flow.
*/
protected void dumpFlowGraph(FlowGraph graph, Term root) {
String name = StringUtil.getShortNameComponent(this.getClass().getName());
name += flowCounter++;
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 DataFlow" + name + " {");
reporter.report(2, " label=\"Dataflow: " + name + "\\n" + rootName +
"\"; fontsize=20; center=true; ratio=auto; size = \"8.5,11\";");
// Loop around the nodes...
for (Peer p : graph.peers()) {
// dump out this node
reporter.report(2,
p.hashCode() + " [ label = \"" +
StringUtil.escape(p.node.toString()) + "\\n(" +
StringUtil.escape(StringUtil.getShortNameComponent(p.node.getClass().getName()))+ ")\" ];");
// dump out the successors.
for (Edge q : p.succs) {
reporter.report(2,
q.getTarget().hashCode() + " [ label = \"" +
StringUtil.escape(q.getTarget().node.toString()) + " (" +
StringUtil.escape(StringUtil.getShortNameComponent(q.getTarget().node.getClass().getName()))+ ")\" ];");
String label = q.getKey().toString();
if (p.outItems != null) {
label += "\\n" + p.outItems.get(q.getKey());
}
else {
label += "\\n[no dataflow available]";
}
reporter.report(2, p.hashCode() + " -> " + q.getTarget().hashCode() +
" [label=\"" + label + "\"];");
}
}
reporter.report(2, "}");
}
}