package org.jf.dexlib.Code.Analysis.graphs; import gnu.trove.TIntObjectHashMap; import gnu.trove.TIntStack; import gnu.trove.TObjectIntHashMap; import java.util.BitSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import org.jgrapht.DirectedGraph; import org.jgrapht.EdgeFactory; import org.jgrapht.graph.SimpleDirectedGraph; /** * Computation of dominators in a flow graph. Algorithm see "A fast algorithm * for finding dominators in a flowgraph" from Lengauer and Tarjan, TOPLAS 1979 * * The dominators (DOM) of a node n in a directed graph with a unique start node (called flowgraph) * are all nodes that lie on every path from the start node to node n. Including the node n itself. * So when you cut the graph at any of node n's dominators, n cannot be reached from the * start anymore. * * Strict dominators (SDOM) of a node n are the same as dominators of node n, excluding the node n itself. * * As all nodes in the flowgraph have to be reachable from the start node (per definition), every node * has at least a single strict dominator - except the start node itself. * * So for each node n the set of strict dominantors SDOM(n) always contains a single dominator * called immediate dominator (IDOM). This is the only node in SDOM(n) that does not dominate any other * node in SDOM(n). There is always exactly one node that fulfills this property (theres a proof -> google) * This node the dominator that is "closest" to n. * * E.g. * <pre> * start -> n1 -> n2 -> n -\ * \-> n3 -> n4 -> n5 * * DOM(n) = {start, n1, n2, n} * SDOM(n) = {start, n1, n2} * IDOM(n) = n2 * * SDOM(n5) = {start, n1} * IDOM(n5) = n1 * </pre> * * As every node has a single immediate dominator, we can construct a graph in tree form where the * parent of each node is its immediate dominator. This tree is called dominator tree. * * E.g. * <pre> * start -> n1 -> n2 -> n * | \-> n3 -> n4 * \-> n5 * </pre> * * @author Juergen Graf <juergen.graf@gmail.com> * * @param <V> Type of the nodes in the flowgraph * @param <E> Type of the edges in the flowgraph */ public class Dominators<V, E> { public static <Y, Z> Dominators<Y, Z> compute(DirectedGraph<Y, Z> graph, Y entry) { Dominators<Y, Z> dom = new Dominators<Y, Z>(graph, entry); dom.compute(); assert dom.assertConsistence(); return dom; } private final DirectedGraph<V, E> graph; private final V start; @SuppressWarnings("unchecked") private Dominators(DirectedGraph<V, E> graph, V start) { this.graph = graph; this.start = start; this.dfsnum2node = (V[]) new Object[graph.vertexSet().size()]; } /** * Stores the parent node of each node during dfs computation. Nodes are identified * by their dfs number. */ private final Map<V, V> idom = new HashMap<V, V>();; /** * Contains the mapping from dfs number to node */ private final V[] dfsnum2node; public V getIDom(V node) { return idom.get(node); } private Map<V, BitSet> idom2domiated; private final Iterable<V> emptyIterable = new DFSNumNodeIterable(new BitSet()); public Iterable<V> getNodesWithIDom(V node) { if (idom2domiated == null) { // build datastructure on demand idom2domiated = new HashMap<V, BitSet>(); // start from 1 - omit the root node at 0 for (int i = 1; i < dfsnum2node.length; i++) { final V curr = dfsnum2node[i]; final V idominator = idom.get(curr); BitSet dominates = idom2domiated.get(idominator); if (dominates == null) { dominates = new BitSet(); idom2domiated.put(idominator, dominates); } dominates.set(i); } } final BitSet dominated = idom2domiated.get(node); return (dominated == null ? emptyIterable : new DFSNumNodeIterable(dominated)); } public DomTree<V> getDominationTree() { DomTree<V> domTree = new DomTree<V>(); for (V node : graph.vertexSet()) { domTree.addVertex(node); } for (V node : graph.vertexSet()) { V idom = getIDom(node); if (idom != null) { domTree.addEdge(idom, node); } } return domTree; } public static class DomTree<V> extends SimpleDirectedGraph<V, DomEdge> { private static final long serialVersionUID = 1445142467229185713L; private DomTree() { super(DomEdge.class); } public String toString() { return "IDOM Tree"; } } public static class DomEdge { public DomEdge() {} public String toString() { return "IDOM"; } } private class DFSNumNodeIterable implements Iterable<V> { private final BitSet nodes; public DFSNumNodeIterable(BitSet nodes) { this.nodes = nodes; } public Iterator<V> iterator() { return new Iterator<V>() { int index = 0; public boolean hasNext() { return nodes.nextSetBit(index) >= 0; } public V next() { if (index < 0) { throw new NoSuchElementException(); } index = nodes.nextSetBit(index); if (index < 0) { throw new NoSuchElementException(); } index++; return dfsnum2node[index - 1]; } public void remove() { throw new UnsupportedOperationException(); } }; } } private void compute() { // first computation step: dfs discover time traversal of flow graph final DomDFSNumWalker walker = new DomDFSNumWalker(); walker.traverseDFS(start); // walker.semi now contains mapping from node to dfs number (reverse mapping of dfsnum2node) final Forest<V> forest = new Forest<V>(walker.semi); // Maps a semidominator to a set of nodes it semidominates. Identified by dfsnum. final TIntObjectHashMap<BitSet> bucket = new TIntObjectHashMap<BitSet>(); // for all nodes except the root node (at index 0) we compute the dominator for (int dfsW = dfsnum2node.length - 1; dfsW > 0; dfsW--) { final V w = dfsnum2node[dfsW]; if (w == null) { throw new IllegalStateException("Null node at dfsW=" + dfsW + " of a total of " + dfsnum2node.length + ". This may happen when unreachable code is in the cfg."); } // step 2 for (final E predEdge : graph.incomingEdgesOf(w)) { final V v = graph.getEdgeSource(predEdge); // u = EVAL(v) final V u = forest.eval(v); // if semi(u) < semi(w) then semi(w) := semi(u) final int semiW = walker.semi.get(w); final int semiU = walker.semi.get(u); if (semiU < semiW) { walker.semi.put(w, semiU); } } // add w to bucket(vertex(semi(w))) final int semiW = walker.semi.get(w); BitSet bs = bucket.get(semiW); if (bs == null) { bs = new BitSet(dfsnum2node.length); bucket.put(semiW, bs); } bs.set(dfsW); // LINK(parent(w), w) final int parentNum = walker.parent.get(w); final V parentW = dfsnum2node[parentNum]; forest.link(parentW, w); // step 3 // for each v in bucket(parent(w)) do final BitSet dominated = bucket.get(parentNum); if (dominated == null) { continue; } for (int i = dominated.nextSetBit(0); i >= 0; i = dominated.nextSetBit(i + 1)) { final V v = dfsnum2node[i]; // delete v from bucket(parent(w)) dominated.clear(i); // u := EVAL(v) final V u = forest.eval(v); // dom(v) := if semi(u) < semi(v) then u else parent(w) final int semiU = walker.semi.get(u); final int semiV = walker.semi.get(v); if (semiU < semiV) { idom.put(v, u); } else { idom.put(v, parentW); } } } // step 4 // for i := 2 to n do -> note that our dfs numbers start at 0 not 1 for (int i = 1; i < dfsnum2node.length; i++) { // w := vertex(i) final V w = dfsnum2node[i]; // if dom(w) != vertex(semi(w)) then dom(w) := dom(dom(w)) final V domW = idom.get(w); final V semiW = dfsnum2node[walker.semi.get(w)]; if (domW != semiW) { final V domdomW = idom.get(domW); idom.put(w, domdomW); } } // dom(r) : = 0 idom.put(start, null); } private final class DomDFSNumWalker extends GraphWalker<V, E> { /* * This variable has 2 usages depending on the state of the computation: * 1. It contains mapping from node to dfsnum after the first step * 2. It contains the semidominator of a node later on */ final TObjectIntHashMap<V> semi = new TObjectIntHashMap<V>(); /* * Stores the parent node of each node during dfs computation. Nodes are identified * by their dfs number. */ final TObjectIntHashMap<V> parent = new TObjectIntHashMap<V>(); /* * Remembers the dfsnumber of the predecessor in the dfs traversal. */ private final TIntStack pred = new TIntStack(); private int dfsnum = 0; public DomDFSNumWalker() { super(graph); } @Override public void discover(V current) { // changes value of Dominators.dfnum2node - side effect... dfsnum2node[dfsnum] = current; semi.put(current, dfsnum); if (pred.size() > 0) { final int parentDFSnum = pred.peek(); parent.put(current, parentDFSnum); } else { parent.put(current, -1); } pred.push(dfsnum); dfsnum++; } @Override public void finish(V node) { pred.pop(); } } private static final class ForestEdge {} private static final class ForestEdgeFactory<V> implements EdgeFactory<V, ForestEdge> { public ForestEdge createEdge(V sourceVertex, V targetVertex) { return new ForestEdge(); } } private static final class Forest<V> extends SimpleDirectedGraph<V, ForestEdge> { private final TObjectIntHashMap<V> semi; public Forest(TObjectIntHashMap<V> semi) { super(new ForestEdgeFactory<V>()); this.semi = semi; } private static final long serialVersionUID = 514793894028572698L; public V eval(final V node) { if (!containsVertex(node)) { // a node can only be root, if it has no been added. return node; } final Set<ForestEdge> in = incomingEdgesOf(node); if (in.size() == 0) { // node is root return node; } else { return minSemiOnPathToRoot(node, node, semi.get(node)); } } private V minSemiOnPathToRoot(final V node, final V currentMin, final int currentSemi) { final Set<ForestEdge> in = incomingEdgesOf(node); final int preds = in.size(); if (preds == 0) { return currentMin; } else if (preds == 1) { final ForestEdge predEdge = in.iterator().next(); final V pred = getEdgeSource(predEdge); final int nodeSemi = semi.get(node); if (nodeSemi < currentSemi) { return minSemiOnPathToRoot(pred, node, nodeSemi); } else { return minSemiOnPathToRoot(pred, currentMin, currentSemi); } } else { throw new IllegalStateException("Not a tree: " + preds + " preds of " + node); } } public void link(final V v, final V w) { addVertex(v); addVertex(w); addEdge(v, w); } } public final boolean assertConsistence() { DomTree<V> domTree = getDominationTree(); boolean consistent = true; for (V node : domTree.vertexSet()) { Set<V> treeDom = new HashSet<V>(); for (Dominators.DomEdge edge : domTree.outgoingEdgesOf(node)) { treeDom.add(domTree.getEdgeTarget(edge)); } Set<V> quickDom = new HashSet<V>(); for (V qdom : getNodesWithIDom(node)) { quickDom.add(qdom); } if (!quickDom.containsAll(treeDom)) { consistent = false; System.out.print("ERR: Tree has additional nodes: "); Set<V> clone = new HashSet<V>(treeDom); clone.removeAll(quickDom); for (V additional : clone) { System.out.print(additional + "; "); } System.out.println(); } if (!treeDom.containsAll(quickDom)) { consistent = false; System.out.print("ERR: Quick has additional nodes: "); Set<V> clone = new HashSet<V>(quickDom); clone.removeAll(treeDom); for (V additional : clone) { System.out.print(additional + "; "); } System.out.println(); } } return consistent; } }