package polyglot.visit;
import polyglot.ast.*;
import polyglot.types.*;
import polyglot.util.*;
import java.util.*;
public class FlowGraph {
/**
* Maps from AST nodes to path maps and hence to <code>Peer</code>s
* that represent occurrences of the
* AST node in the flow graph. In particular, <code>peerMap</code>
* maps <code>IdentityKey(Node)</code>s to path maps. A path map is a
* map from paths (<code>ListKey(List of Terms)</code>) to
* <code>Peer</code>s. In particular, if <code>n</code> is an AST
* node in a finally block, then there will be a <code>Peer</code> of
* <code>n</code> for each possible path to the finally block, and the
* path map records which <code>Peer</code> corresponds to which path.
* If <code>n</code> does not occur in a finally block, then the path
* map should have only a single entry, from an empty list to the
* unique <code>Peer</code> for <code>n</code>.
*
* <p>
* <b>WARNING</b>: the AST must be a tree, not a DAG. Otherwise the
* same peer may be used for a node that appears at multiple points in
* the AST. These points may have different data flows.
* </p>
*/
protected Map peerMap;
/**
* The root of the AST that this is a flow graph for.
*/
protected Term root;
/**
* Is the flow in this flow graph forward or backward?
*/
protected boolean forward;
FlowGraph(Term root, boolean forward) {
this.root = root;
this.forward = forward;
this.peerMap = new HashMap();
}
public Term startNode() { return forward ? root.entry() : root; }
public Term finishNode() { return forward ? root : root.entry(); }
public Term entryNode() { return root.entry(); }
public Term exitNode() { return root; }
public Term root() { return root; }
public boolean forward() { return forward; }
public Collection pathMaps() {
return peerMap.values();
}
public Map pathMap(Node n) {
return (Map) peerMap.get(new IdentityKey(n));
}
/**
* Return a collection of all <code>Peer</code>s in this flow graph.
*/
public Collection peers() {
Collection c = new ArrayList();
for (Iterator i = peerMap.values().iterator(); i.hasNext(); ) {
Map m = (Map) i.next();
for (Iterator j = m.values().iterator(); j.hasNext(); ) {
c.add(j.next());
}
}
return c;
}
/**
* Retrieve the <code>Peer</code> for the <code>Term n</code>, where
* <code>n</code> does not appear in a finally block. If no such Peer
* exists, then one will be created.
*
* @param df unused; for legacy purposes only?
*/
public Peer peer(Term n, DataFlow df) {
return peer(n, Collections.EMPTY_LIST, df);
}
/**
* Return a collection of all of the <code>Peer</code>s for the given
* <code>Term n</code>.
*/
public Collection peers(Term n) {
IdentityKey k = new IdentityKey(n);
Map pathMap = (Map) peerMap.get(k);
if (pathMap == null) {
return Collections.EMPTY_LIST;
}
return pathMap.values();
}
/**
* Retrieve the <code>Peer</code> for the <code>Term n</code> that is
* associated with the given path to the finally block. (A term that occurs
* in a finally block has one Peer for each possible path to that finally
* block.) If no such Peer exists, then one will be created.
*
* @param df unused; for legacy purposes only?
*/
public Peer peer(Term n, List path_to_finally, DataFlow df) {
IdentityKey k = new IdentityKey(n);
Map pathMap = (Map) peerMap.get(k);
if (pathMap == null) {
pathMap = new HashMap();
peerMap.put(k, pathMap);
}
ListKey lk = new ListKey(path_to_finally);
Peer p = (Peer) pathMap.get(lk);
if (p == null) {
p = new Peer(n, path_to_finally);
pathMap.put(lk, p);
}
return p;
}
/**
* This class provides an identifying label for edges in the flow graph.
* Thus, the condition of an if statement will have at least two edges
* leaving it (in a forward flow graph): one will have the EdgeKey
* FlowGraph.EDGE_KEY_TRUE, and is the flow that is taken when the condition
* evaluates to true, and one will have the EdgeKey FlowGraph.EDGE_KEY_FALSE,
* and is the flow that is taken when the condition evaluates to false.
*
* The differentiation of the flow graph edges allows for a finer grain
* data flow analysis, as the dataflow equations can incorporate the
* knowledge that a condition is true or false on certain flow paths.
*/
public static class EdgeKey {
protected Object o;
protected EdgeKey(Object o) {
this.o = o;
}
public int hashCode() {
return o.hashCode();
}
public boolean equals(Object other) {
return (other instanceof EdgeKey) &&
(((EdgeKey)other).o.equals(this.o));
}
public String toString() {
return o.toString();
}
}
/**
* This class extends EdgeKey and is the key for edges that are
* taken when an exception of type t is thrown. Thus, the flow from
* line 2 in the example below to the catch block (line 4) would have an
* ExceptionEdgeKey constructed with the Type representing
* NullPointerExceptions.
*
* <pre>
* ...
* try { // line 1
* o.foo(); // line 2
* } // line 3
* catch (NullPointerException e) { // line 4
* ...
* }
* ...
* </pre>
*/
public static class ExceptionEdgeKey extends EdgeKey {
public ExceptionEdgeKey(Type t) {
super(t);
}
public Type type() {
return (Type) o;
}
public String toString() {
return (type().isClass() ? type().toClass().name() : type().toString() );
}
}
/**
* This EdgeKey is the EdgeKey for edges where the expression evaluates
* to true.
*/
public static final EdgeKey EDGE_KEY_TRUE = new EdgeKey("true");
/**
* This EdgeKey is the EdgeKey for edges where the expression evaluates
* to false.
*/
public static final EdgeKey EDGE_KEY_FALSE = new EdgeKey("false");
/**
* This EdgeKey is the EdgeKey for edges where the flow is not suitable
* for EDGE_KEY_TRUE, EDGE_KEY_FALSE or an
* ExceptionEdgeKey, such as the edges from a switch
* statement to its cases and
* the flow from a sink node in the control flow graph.
*/
public static final EdgeKey EDGE_KEY_OTHER = new EdgeKey("");
/**
* This class represents an edge in the flow graph. The target of the edge
* is either the head or the tail of the edge, depending on how the Edge is
* used. Thus, the target field in Edges in the collection Peer.preds is the
* source Peer, while the target field in Edges in the collection Peer.succs
* is the destination Peer of edges.
*
* Each Edge has an EdgeKey, which identifies when flow uses that edge in
* the flow graph. See EdgeKey for more information.
*/
public static class Edge {
protected Edge(EdgeKey key, Peer target) {
this.key = key;
this.target = target;
}
public EdgeKey getKey() {
return key;
}
public Peer getTarget() {
return target;
}
protected EdgeKey key;
protected Peer target;
public String toString() {
return "(" + key + ")" + target;
}
}
/**
* A <code>Peer</code> is an occurance of an AST node in a flow graph.
* For most AST nodes, there will be only one Peer for each AST node.
* However, if the AST node occurs in a finally block, then there will be
* multiple <code>Peer</code>s for that AST node, one for each possible
* path to the finally block. This is becuase flow graphs for finally blocks
* are copied, one copy for each possible path to the finally block.
*/
public static class Peer {
protected DataFlow.Item inItem; // Input Item for dataflow analysis
protected Map outItems; // Output Items for dataflow analysis, a map from EdgeKeys to DataFlowlItems
protected Term node; // The AST node that this peer is an occurance of.
protected List succs; // List of successor Edges
protected List preds; // List of predecessor Edges
protected List path_to_finally; // the path to the finally block that
// uniquely distinguishes this Peer
// from the other Peers for the AST node.
/**
* Set of all the different EdgeKeys that occur in the Edges in the
* succs. This Set is lazily constructed, as needed, by the
* method succEdgeKeys()
*/
private Set succEdgeKeys;
public Peer(Term node, List path_to_finally) {
this.node = node;
this.path_to_finally = path_to_finally;
this.inItem = null;
this.outItems = null;
this.succs = new ArrayList();
this.preds = new ArrayList();
this.succEdgeKeys = null;
}
/** The successor Edges. */
public List succs() { return succs; }
/** The predecessor Edges. */
public List preds() { return preds; }
/** The node for which this is a peer. */
public Term node() { return node; }
/**
* The input data flow item. Should only be called
* after data flow analysis is performed.
*/
public DataFlow.Item inItem() { return inItem; }
/**
* The output item for a particular EdgeKey. Should only be called
* after data flow analysis is performed.
*/
public DataFlow.Item outItem(EdgeKey key) {
return (DataFlow.Item) outItems.get(key);
}
public String toString() {
return node + "[" + hashCode() + ": " + path_to_finally + "]";
}
public Set succEdgeKeys() {
if (this.succEdgeKeys == null) {
// the successor edge keys have not yet been calculated. do it
// now.
this.succEdgeKeys = new HashSet();
for (Iterator iter = this.succs.iterator(); iter.hasNext(); ) {
Edge e = (Edge)iter.next();
this.succEdgeKeys.add(e.getKey());
}
if (this.succEdgeKeys.isEmpty()) {
// There are no successors for this node. Add in the OTHER
// edge key, so that there is something to map the output
// item from...
this.succEdgeKeys.add(FlowGraph.EDGE_KEY_OTHER);
}
}
return this.succEdgeKeys;
}
}
/**
* Class to be used for inserting Lists in hashtables using collection
* equality (as defined in {@link polyglot.util.CollectionUtil CollectionUtil}).
*/
protected static class ListKey {
protected List list;
ListKey(List list) {
this.list = list;
}
public int hashCode() {
return list.hashCode();
}
public boolean equals(Object other) {
if (other instanceof ListKey) {
ListKey k = (ListKey) other;
return CollectionUtil.equals(list, k.list);
}
return false;
}
}
public String toString() {
StringBuffer sb = new StringBuffer();
Set todo = new HashSet(this.peers());
LinkedList queue = new LinkedList(this.peers(this.startNode()));
while (!queue.isEmpty()) {
Peer p = (Peer)queue.removeFirst();
todo.remove(p);
// sb.append(StringUtil.getShortNameComponent(p.node.getClass().getName()) + " ["+p.node+"]" + "\n");
sb.append(p.node+" (" + p.node.position()+ ")\n");
for (Iterator i = p.succs.iterator(); i.hasNext(); ) {
Edge e = (Edge)i.next();
Peer q = e.getTarget();
sb.append(" -> " + q.node+" (" + q.node.position()+ ")\n");
//sb.append(" " + StringUtil.getShortNameComponent(q.node.getClass().getName()) + " ["+q.node+"]" + "\n");
if (todo.contains(q) && !queue.contains(q)) {
queue.addLast(q);
}
}
if (queue.isEmpty() && !todo.isEmpty()) {
sb.append("\n\n***UNREACHABLE***\n");
queue.addAll(todo);
todo = Collections.EMPTY_SET;
}
}
return sb.toString();
}
}