/** * This class is a piece of test infrastructure. It compares various * varieties of control flow graphs. */ package soot.toolkits.graph; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import soot.Body; import soot.BriefUnitPrinter; import soot.CompilationDeathException; import soot.G; import soot.LabeledUnitPrinter; import soot.Trap; import soot.Unit; import soot.options.Options; import soot.toolkits.graph.ExceptionalUnitGraph.ExceptionDest; import soot.util.Chain; public class GraphComparer { DirectedGraph g1; DirectedGraph g2; /** * Utility interface for keeping track of graph nodes which * are considered to represent equivalent nodes in * the two graphs being compared. */ interface EquivalenceRegistry { /** * @param node a node in one graph. * @return the equivalent node from the other graph. */ Object getEquiv(Object node); } EquivalenceRegistry equivalences = null; /** * {@link EquivalenceRegistry} for comparing two {@link UnitGraph}s * Since the same {@link Unit}s are stored as nodes in both graphs, * equivalence is the same as object equality. */ class EquivalentUnitRegistry implements EquivalenceRegistry { /** * @param node a graph node that represents a {@link Unit}. * @return <tt>node</tt>. */ public Object getEquiv(Object node) { return node; } } /** * {@link EquivalenceRegistry} for comparing two {@link BlockGraph}s. * Two blocks are considered equivalent if they contain exactly the same * list of {@link Unit}s, in the same order. */ static class EquivalentBlockRegistry implements EquivalenceRegistry { private Map equivalenceMap = new HashMap(); /** * Create an {@link EquivalentBlockRegistry} which records the * equivalent blocks in two graphs whose nodes are blocks. To * allow the use of graphs that are loaded from alternate * class paths, the parameters are not required to be instances of * {@link BlockGraph}. They just have to be {@link * DirectedGraph}s whose nodes are instances of some class * that has an <tt>iterator()</tt> method that iterates over * the {@link Unit}s in that block. * * @param g1 The first graph to register. * @param g2 The second graph to register. * @throws IllegalArgumentException if a given {@link Unit} appears * in more than one block of either of the graphs. */ EquivalentBlockRegistry(DirectedGraph g1, DirectedGraph g2) { Map g1UnitToBlock = blockGraphToUnitMap(g1); // We don't need this // map, but we want // to confirm that no // Unit appears in // multiple Blocks. Map g2UnitToBlock = blockGraphToUnitMap(g2); for (Iterator g1it = g1.iterator(); g1it.hasNext(); ) { Object g1Block = g1it.next(); List g1Units = getUnits(g1Block); Object g2Block = g2UnitToBlock.get(g1Units.get(0)); List g2Units = getUnits(g2Block); if (g1Units.equals(g2Units)) { equivalenceMap.put(g1Block, g2Block); equivalenceMap.put(g2Block, g1Block); } } } /** * @param node a graph node that represents a {@link Block}. * @return the node from the other graph being compared which * represents the same block, or <tt>null</tt> if there * is no such node. */ public Object getEquiv(Object node) { return equivalenceMap.get(node); } /** * Return a map from a {@link Unit} in the body represented by * a {@link BlockGraph} to the graph node representing the * block containing that {@link Unit}. * * @param g a graph whose nodes represent lists of {@link Unit}s. * The nodes must have an <tt>iterator()</tt> method which * will iterate over the {@link Unit}s represented by the * node. * @return a {@link Map} from {@link Unit}s to {@link Object}s * that are the graph nodes containing those {@link Unit}s. * @throws IllegalArgumentException should any node of <tt>g</tt> * lack an <tt>iterator()</tt> method or should * any {@link Unit} appear in * more than one node of the graph. */ private static Map blockGraphToUnitMap(DirectedGraph g) throws IllegalArgumentException { Map result = new HashMap(); for (Iterator blockIt = g.iterator(); blockIt.hasNext(); ) { Object block = blockIt.next(); List units = getUnits(block); for (Iterator unitIt = units.iterator(); unitIt.hasNext(); ) { Unit unit = (Unit) unitIt.next(); if (result.containsKey(unit)) { throw new IllegalArgumentException("blockGraphToUnitMap(): adding " + unit.toString() + " twice"); } result.put(unit, block); } } return result; } /** * Return the {@link List} of {@link Unit}s represented by an * object which has an <tt>iterator()</tt> method which * iterates over {@link Unit}s. * * @param block the object which contains a list of {@link Unit}s. * @return the list of {@link Unit}s. */ private static List getUnits(Object block) { Class blockClass = block.getClass(); Class[] emptyParams = new Class[0]; List result = new ArrayList(); try { Method iterMethod = blockClass.getMethod("iterator", emptyParams); for (Iterator it = (Iterator) iterMethod.invoke(block, new Object[0]); it.hasNext(); ) { Unit unit = (Unit) it.next(); result.add(unit); } } catch (NoSuchMethodException e) { throw new IllegalArgumentException("GraphComparer.getUnits(): node lacks iterator() method."); } catch (IllegalAccessException e) { throw new IllegalArgumentException("GraphComparer.getUnits(): inaccessible iterator() method."); } catch (java.lang.reflect.InvocationTargetException e) { throw new IllegalArgumentException("GraphComparer.getUnits(): failed iterator() invocation."); } return result; } } /** * Utility interface for checking whether two graphs of particular types * differ only in the ways we would expect from two graphs * of those types that represent the same {@link Body}. */ interface TypedGraphComparer { /** * @return true if the two graphs represented by this comparer * differ only in expected ways. */ boolean onlyExpectedDiffs(); } TypedGraphComparer subcomparer = null; /** * Return a class that implements <tt>TypedGraphComparer</tt> for * the two graphs being compared by this <tt>GraphComparer</tt>, * or <tt>null</tt>, if there is no comparer available to compare * their types. */ TypedGraphComparer TypedGraphComparerFactory() { class TypeAssignments { // Note that "Alt" graphs are loaded from an alternate // class path, so we need ugly, fragile, special-purpose // hacks to recognize them. ExceptionalUnitGraph exceptionalUnitGraph = null; boolean omitExceptingUnitEdges; ClassicCompleteUnitGraph classicCompleteUnitGraph = null; TrapUnitGraph trapUnitGraph = null; BriefBlockGraph briefBlockGraph = null; ClassicCompleteBlockGraph classicCompleteBlockGraph = null; DirectedGraph altCompleteBlockGraph = null; DirectedGraph altBriefBlockGraph = null; ArrayRefBlockGraph arrayRefBlockGraph = null; DirectedGraph altArrayRefBlockGraph = null; ZonedBlockGraph zonedBlockGraph = null; DirectedGraph altZonedBlockGraph = null; TypeAssignments(DirectedGraph g1, DirectedGraph g2) { this.add(g1); this.add(g2); } // The type-specific add() routine provides a means to // specify the value of omitExceptingUnitEdges // for ExceptionalUnitGraph. We are counting on the // callers to keep straight whether the graph was created // with a non-default value for // omitExceptingUnitEdges, since it doesn't seem // worth saving the value of the boolean used with each // ExceptionalUnitGraph constructed. void add(ExceptionalUnitGraph g, boolean omitExceptingUnitEdges) { this.exceptionalUnitGraph = g; this.omitExceptingUnitEdges = omitExceptingUnitEdges; } void add(DirectedGraph g) { if (g instanceof CompleteUnitGraph) { this.add((ExceptionalUnitGraph) g, false); } else if (g instanceof ExceptionalUnitGraph) { this.add((ExceptionalUnitGraph) g, Options.v().omit_excepting_unit_edges()); } else if (g instanceof ClassicCompleteUnitGraph) { classicCompleteUnitGraph = (ClassicCompleteUnitGraph) g; } else if (g.getClass().getName().endsWith(".CompleteUnitGraph")) { } else if (g instanceof TrapUnitGraph) { trapUnitGraph = (TrapUnitGraph) g; } else if (g.getClass().getName().endsWith(".TrapUnitGraph")) { } else if (g instanceof BriefUnitGraph) { } else if (g.getClass().getName().endsWith(".BriefUnitGraph")) { } else if (g instanceof ExceptionalBlockGraph) { } else if (g instanceof ClassicCompleteBlockGraph) { classicCompleteBlockGraph = (ClassicCompleteBlockGraph) g; } else if (g.getClass().getName().endsWith(".CompleteBlockGraph")) { altCompleteBlockGraph = g; } else if (g instanceof BriefBlockGraph) { briefBlockGraph = (BriefBlockGraph) g; } else if (g.getClass().getName().endsWith(".BriefBlockGraph")) { altBriefBlockGraph = g; } else if (g instanceof ArrayRefBlockGraph) { arrayRefBlockGraph = (ArrayRefBlockGraph) g; } else if (g.getClass().getName().endsWith(".ArrayRefBlockGraph")) { altArrayRefBlockGraph = g; } else if (g instanceof ZonedBlockGraph) { zonedBlockGraph = (ZonedBlockGraph) g; } else if (g.getClass().getName().endsWith(".ZonedBlockGraph")) { altZonedBlockGraph = g; } else { throw new IllegalStateException("GraphComparer.add(): don't know how to add(" + g.getClass().getName() + ')'); } } } TypeAssignments subtypes = new TypeAssignments(g1, g2); if (subtypes.exceptionalUnitGraph != null && subtypes.classicCompleteUnitGraph != null) { return new ExceptionalToClassicCompleteUnitGraphComparer( subtypes.exceptionalUnitGraph, subtypes.classicCompleteUnitGraph, subtypes.omitExceptingUnitEdges); } if (subtypes.exceptionalUnitGraph != null && subtypes.trapUnitGraph != null) { return new ExceptionalToTrapUnitGraphComparer(subtypes.exceptionalUnitGraph, subtypes.trapUnitGraph, subtypes.omitExceptingUnitEdges); } if (subtypes.classicCompleteBlockGraph != null && subtypes.altCompleteBlockGraph != null) { return new BlockToAltBlockGraphComparer(subtypes.classicCompleteBlockGraph, subtypes.altCompleteBlockGraph); } if (subtypes.briefBlockGraph != null && subtypes.altBriefBlockGraph != null) { return new BlockToAltBlockGraphComparer(subtypes.briefBlockGraph, subtypes.altBriefBlockGraph); } if (subtypes.arrayRefBlockGraph != null && subtypes.altArrayRefBlockGraph != null) { return new BlockToAltBlockGraphComparer(subtypes.arrayRefBlockGraph, subtypes.altArrayRefBlockGraph); } if (subtypes.zonedBlockGraph != null && subtypes.altZonedBlockGraph != null) { return new BlockToAltBlockGraphComparer(subtypes.zonedBlockGraph, subtypes.altZonedBlockGraph); } return null; } public GraphComparer(DirectedGraph g1, DirectedGraph g2) { // Since we may be comparing graphs of classes loaded // from alternate class paths, we'll use conventions in // the class names to recognize BlockGraphs. this.g1 = g1; this.g2 = g2; String g1ClassName = g1.getClass().getName(); String g2ClassName = g2.getClass().getName(); if (g1ClassName.endsWith("BlockGraph") && g2ClassName.endsWith("BlockGraph")) { equivalences = new EquivalentBlockRegistry(g1, g2); } else { equivalences = new EquivalentUnitRegistry(); } subcomparer = TypedGraphComparerFactory(); } /** * Checks that the two graphs in the GraphComparer differ only in * ways that the GraphComparer expects that they should differ, * given the types of graphs they are, assuming that the two * graphs represent the same body. */ public boolean onlyExpectedDiffs() { if (subcomparer == null) { return equal(); } else { return subcomparer.onlyExpectedDiffs(); } } /** * Checks if a graph's edge set is consistent. * * @param g the {@link DirectedGraph} whose edge set is to be * checked. * * @return <tt>true</tt> if <tt>g</tt>'s edge set is consistent * (meaning that <tt>x</tt> is recorded as a predecessor of <tt>y</tt> * if and only if <tt>y</tt> is recorded as a successor of <tt>x</tt>). */ public static boolean consistentGraph(DirectedGraph g) { for (Iterator nodeIt = g.iterator(); nodeIt.hasNext(); ) { Object node = nodeIt.next(); for (Iterator predIt = g.getPredsOf(node).iterator(); predIt.hasNext(); ) { Object pred = predIt.next(); if (! g.getSuccsOf(pred).contains(node)) { return false; } } for (Iterator succIt = g.getSuccsOf(node).iterator(); succIt.hasNext(); ) { Object succ = succIt.next(); if (! g.getPredsOf(succ).contains(node)) { return false; } } } return true; } /** * Compares this {@link GraphComparer}'s two {@link DirectedGraph}s. * * @return <tt>true</tt> if the two graphs have * the same nodes, the same lists of heads and tails, and the * same sets of edges, <tt>false</tt> otherwise. */ public boolean equal() { if ((! consistentGraph(g1)) || (! consistentGraph(g2))) { throw new CompilationDeathException( CompilationDeathException.COMPILATION_ABORTED, "Cannot compare inconsistent graphs"); } if (! equivLists(g1.getHeads(), g2.getHeads())) { return false; } if (! equivLists(g1.getTails(), g2.getTails())) { return false; } return equivPredsAndSuccs(); } /** * <p>Compares the predecessors and successors of corresponding * nodes of this {@link GraphComparer}'s two {@link * DirectedGraph}s. This is factored out for sharing by {@link * #equal()} and {@link BlockToAltBlockGraphComparer#onlyExpectedDiffs}. * * @return <tt>true</tt> if the predecessors and successors of each * node in one graph are equivalent to the predecessors and successors * of the equivalent node in the other graph. */ protected boolean equivPredsAndSuccs() { if (g1.size() != g2.size()) { return false; } // We know the two graphs have the same size, so we only need // to iterate through one's nodes to see if the two match. for (Iterator g1it = g1.iterator(); g1it.hasNext(); ) { Object g1node = g1it.next(); try { if (! equivLists(g1.getSuccsOf(g1node), g2.getSuccsOf(equivalences.getEquiv(g1node)))) { return false; } if (! equivLists(g1.getPredsOf(g1node), g2.getPredsOf(equivalences.getEquiv(g1node)))) { return false; } } catch (RuntimeException e) { if (e.getMessage() != null && e.getMessage().startsWith("Invalid unit ")) { return false; } else { throw e; } } } return true; } /** * Report the differences between the two {@link DirectedGraph}s. * * @param graph1Label a string to be used to identify the first * graph (<tt>g1</tt> in the call to {@link * GraphComparer#GraphComparer()}) in the report of differences. * * @param graph2Label a string to be used to identify the second * graph (<tt>g2</tt> in the call to {@link * GraphComparer@GraphComparer()}) in the report of differences . * * @return a {@link String} summarizing the differences * between the two graphs. */ public String diff(String graph1Label, String graph2Label) { StringBuffer report = new StringBuffer(); LabeledUnitPrinter printer1 = makeUnitPrinter(g1); LabeledUnitPrinter printer2 = makeUnitPrinter(g2); diffList(report, printer1, printer2, "HEADS", g1.getHeads(), g2.getHeads()); diffList(report, printer1, printer2, "TAILS", g1.getTails(), g2.getTails()); for (Iterator g1it = g1.iterator(); g1it.hasNext(); ) { Object g1node = g1it.next(); Object g2node = equivalences.getEquiv(g1node); String g1string = nodeToString(g1node, printer1); List g1succs = g1.getSuccsOf(g1node); List g1preds = g1.getPredsOf(g1node); List g2succs = null; List g2preds = null; try { if (g2node != null) { g2preds = g2.getPredsOf(g2node); } } catch (RuntimeException e) { if (e.getMessage() != null && e.getMessage().startsWith("Invalid unit ")) { // No preds entry for g1node in g2 } else { throw e; } } diffList(report, printer1, printer2, g1string + " PREDS", g1preds, g2preds); try { if (g2node != null) { g2succs = g2.getSuccsOf(g2node); } } catch (RuntimeException e) { if (e.getMessage() != null && e.getMessage().startsWith("Invalid unit ")) { // No succs entry for g1node in g2 } else { throw e; } } diffList(report, printer1, printer2, g1string + " SUCCS", g1succs, g2succs); } for (Iterator g2it = g2.iterator(); g2it.hasNext(); ) { // In this loop we Only need to report the cases where // there is no g1 entry at all, cases where there is an // entry in both graphs but with differing lists of preds // or succs will have already been reported by the loop over // g1's nodes. Object g2node = g2it.next(); Object g1node = equivalences.getEquiv(g2node); String g2string = nodeToString(g2node, printer2); List g2succs = g2.getSuccsOf(g2node); List g2preds = g2.getPredsOf(g2node); try { if (g1node != null) { g1.getPredsOf(g1node); } } catch (RuntimeException e) { if (e.getMessage() != null && e.getMessage().startsWith("Invalid unit ")) { diffList(report, printer1, printer2, g2string + " PREDS", null, g2preds); } else { throw e; } } try { if (g1node != null) { g1.getSuccsOf(g1node); } } catch (RuntimeException e) { if (e.getMessage() != null && e.getMessage().startsWith("Invalid unit ")) { diffList(report, printer1, printer2, g2string + " SUCCS" , null, g2succs); } else { throw e; } } } if (report.length() > 0) { String leader1 = "*** "; String leader2 = "\n--- "; String leader3 = "\n"; StringBuffer result = new StringBuffer(leader1.length() + graph1Label.length() + leader2.length() + graph2Label.length() + leader3.length() + report.length()); result.append(leader1) .append(graph1Label) .append(leader2) .append(graph2Label) .append(leader3) .append(report); return result.toString(); } else { return ""; } } /** * Class for comparing a {@link TrapUnitGraph} to an {@link * ExceptionalUnitGraph}. */ class ExceptionalToTrapUnitGraphComparer implements TypedGraphComparer { protected ExceptionalUnitGraph exceptional; protected TrapUnitGraph cOrT; // ClassicCompleteUnitGraph Or TrapUnitGraph. protected boolean omitExceptingUnitEdges; /** * Indicates whether {@link #predOfTrappedThrower()} should * return false for edges from head to tail when the only one * of head's successors which throws an exception caught by * tail is the first unit trapped by tail (this distinguishes * ClassicCompleteUnitGraph from TrapUnitGraph). */ protected boolean predOfTrappedThrowerScreensFirstTrappedUnit; ExceptionalToTrapUnitGraphComparer(ExceptionalUnitGraph exceptional, TrapUnitGraph cOrT, boolean omitExceptingUnitEdges) { this.exceptional = exceptional; this.cOrT = cOrT; this.omitExceptingUnitEdges = omitExceptingUnitEdges; this.predOfTrappedThrowerScreensFirstTrappedUnit = false; } public boolean onlyExpectedDiffs() { if (exceptional.size() != cOrT.size()) { if (Options.v().verbose()) G.v().out.println("ExceptionalToTrapUnitComparer.onlyExpectedDiffs(): sizes differ" + exceptional.size() + " " + cOrT.size()); return false; } if (! (exceptional.getHeads().containsAll(cOrT.getHeads()) && cOrT.getHeads().containsAll(exceptional.getHeads())) ) { if (Options.v().verbose()) G.v().out.println("ExceptionalToTrapUnitComparer.onlyExpectedDiffs(): heads differ"); return false; } if (! (exceptional.getTails().containsAll(cOrT.getTails()))) { return false; } for (Iterator<Unit> tailIt = exceptional.getTails().iterator(); tailIt.hasNext(); ) { Unit tail = tailIt.next(); if ((! cOrT.getTails().contains(tail)) && (! trappedReturnOrThrow(tail))) { if (Options.v().verbose()) G.v().out.println("ExceptionalToTrapUnitComparer.onlyExpectedDiffs(): " + tail.toString() + " is not a tail in cOrT, but not a trapped Return or Throw either"); return false; } } // Since we've already confirmed that the two graphs have // the same number of nodes, we only need to iterate through // the nodes of one of them --- if they don't have exactly the same // set of nodes, this single iteration will reveal some // node in cOrT that is not in exceptional. for (Iterator nodeIt = cOrT.iterator(); nodeIt.hasNext(); ) { Unit node = (Unit) nodeIt.next(); try { List cOrTSuccs = cOrT.getSuccsOf(node); List exceptionalSuccs = exceptional.getSuccsOf(node); for (Iterator it = cOrTSuccs.iterator(); it.hasNext(); ) { Unit cOrTSucc = (Unit) it.next(); if ((! exceptionalSuccs.contains(cOrTSucc)) && (! cannotReallyThrowTo(node, cOrTSucc))) { if (Options.v().verbose()) G.v().out.println("ExceptionalToTrapUnitComparer.onlyExpectedDiffs(): " + cOrTSucc.toString() + " is not exceptional successor of " + node.toString() + " even though " + node.toString() + " can throw to it"); return false; } } for (Iterator it = exceptionalSuccs.iterator(); it.hasNext(); ) { Unit exceptionalSucc = (Unit) it.next(); if ((! cOrTSuccs.contains(exceptionalSucc)) && (! predOfTrappedThrower(node, exceptionalSucc))) { if (Options.v().verbose()) G.v().out.println("ExceptionalToTrapUnitComparer.onlyExpectedDiffs(): " + exceptionalSucc.toString() + " is not TrapUnitGraph successor of " + node.toString() + " even though " + node.toString() + " is not a predOfTrappedThrower or predOfTrapBegin"); return false; } } List cOrTPreds = cOrT.getPredsOf(node); List exceptionalPreds = exceptional.getPredsOf(node); for (Iterator it = cOrTPreds.iterator(); it.hasNext(); ) { Unit cOrTPred = (Unit) it.next(); if ((! exceptionalPreds.contains(cOrTPred)) && (! cannotReallyThrowTo(cOrTPred, node))) { if (Options.v().verbose()) G.v().out.println("ExceptionalToTrapUnitComparer.onlyExpectedDiffs(): " + cOrTPred.toString() + " is not ExceptionalUnitGraph predecessor of " + node.toString() + " even though " + cOrTPred.toString() + " can throw to " + node.toString()); return false; } } for (Iterator it = exceptionalPreds.iterator(); it.hasNext(); ) { Unit exceptionalPred = (Unit) it.next(); if ((! cOrTPreds.contains(exceptionalPred)) && (! predOfTrappedThrower(exceptionalPred, node))) { if (Options.v().verbose()) G.v().out.println("ExceptionalToTrapUnitComparer.onlyExpectedDiffs(): " + exceptionalPred.toString() + " is not COrTUnitGraph predecessor of " + node.toString() + " even though " + exceptionalPred.toString() + " is not a predOfTrappedThrower"); return false; } } } catch (RuntimeException e) { e.printStackTrace(System.err); if (e.getMessage() != null && e.getMessage().startsWith("Invalid unit ")) { if (Options.v().verbose()) G.v().out.println("ExceptionalToTrapUnitComparer.onlyExpectedDiffs(): " + node.toString() + " is not in ExceptionalUnitGraph at all"); // node is not in exceptional graph. return false; } else { throw e; } } } return true; } /** * <p>A utility method for confirming that a node which is * considered a tail by a {@link ExceptionalUnitGraph} but not by the * corresponding {@link TrapUnitGraph} or {@link * ClassicCompleteUnitGraph} is a return instruction or throw * instruction with a {@link Trap} handler as its successor in the * {@link TrapUnitGraph} or {@link ClassicCompleteUnitGraph}.</p> * * @param node the node which is considered a tail by * <tt>exceptional</tt> but not by <tt>cOrT</tt>. * * @return <tt>true</tt> if <tt>node</tt> is a return instruction * which has a trap handler as its successor in <tt>cOrT</tt>.\ */ protected boolean trappedReturnOrThrow(Unit node) { if (! ((node instanceof soot.jimple.ReturnStmt) || (node instanceof soot.jimple.ReturnVoidStmt) || (node instanceof soot.baf.ReturnInst) || (node instanceof soot.baf.ReturnVoidInst) || (node instanceof soot.jimple.ThrowStmt) || (node instanceof soot.baf.ThrowInst))) { return false; } List succsUnaccountedFor = new ArrayList(cOrT.getSuccsOf(node)); if (succsUnaccountedFor.size() <= 0) { return false; } for (Iterator trapIt = cOrT.getBody().getTraps().iterator(); trapIt.hasNext(); ) { Trap trap = (Trap) trapIt.next(); succsUnaccountedFor.remove(trap.getHandlerUnit()); } return (succsUnaccountedFor.size() == 0); } /** * A utility method for confirming that an edge which is in a * {@link TrapUnitGraph} or {@link ClassicCompleteUnitGraph} but * not the corresponding {@link ExceptionalUnitGraph} satisfies the * criterion we would expect of such an edge: that it leads from a * unit to a handler, that the unit might be expected to trap to * the handler based solely on the range of Units trapped by the * handler, but that there is no way for the particular exception * that the handler catches to be thrown at this point. * * @param head the graph node that the edge leaves. * @param tail the graph node that the edge leads to. * @return true if <tt>head</tt> cannot really be associated with * an exception that <tt>tail</tt> catches, false otherwise. */ protected boolean cannotReallyThrowTo(Unit head, Unit tail) { List tailsTraps = returnHandlersTraps(tail); // Check if head is in one of the traps' protected // area, but either cannot throw an exception that the // trap catches, or is not included in ExceptionalUnitGraph // because it has no side-effects: Collection headsDests = exceptional.getExceptionDests(head); for (Iterator it = tailsTraps.iterator(); it.hasNext(); ) { Trap tailsTrap = (Trap) it.next(); if (amongTrappedUnits(head, tailsTrap) && ((! destCollectionIncludes(headsDests, tailsTrap)) || ((! ExceptionalUnitGraph.mightHaveSideEffects(head)) && omitExceptingUnitEdges))) { return true; } } return false; } /** * A utility method for determining if a {@link Unit} is among * those protected by a particular {@link Trap}.. * * @param unit the {@link Unit} being asked about. * * @param trap the {@link Trap} being asked about. * * @return <tt>true</tt> if <tt>unit</tt> is protected by * <tt>trap</tt>, false otherwise. */ protected boolean amongTrappedUnits(Unit unit, Trap trap) { Chain units = exceptional.getBody().getUnits(); for (Iterator it = units.iterator(trap.getBeginUnit(), units.getPredOf(trap.getEndUnit())); it.hasNext(); ) { Unit u = (Unit) it.next(); if (u == unit) { return true; } } return false; } /** * A utility method for confirming that an edge which is in a * {@link CompleteUnitGraph} but not the corresponding {@link * TrapUnitGraph} satisfies the criteria we would expect of such * an edge: that the tail of the edge is a {@link Trap} handler, * and that the head of the edge is not itself in the * <tt>Trap</tt>'s protected area, but is the predecessor of a * {@link Unit}, call it <tt>u</tt>, such that <tt>u</tt> is a * <tt>Unit</tt> in the <tt>Trap</tt>'s protected area and * <tt>u</tt> might throw an exception that the <tt>Trap</tt> * catches. * * @param head the graph node that the edge leaves. * @param tail the graph node that the edge leads to. * @return a {@link List} of {@link Trap}s such that <tt>head</tt> is * a predecessor of a {@link Unit} that might throw an exception * caught by {@link Trap}. */ protected boolean predOfTrappedThrower(Unit head, Unit tail) { // First, ensure that tail is a handler. List tailsTraps = returnHandlersTraps(tail); if (tailsTraps.size() == 0) { if (Options.v().verbose()) G.v().out.println("trapsReachedViaEdge(): " + tail.toString() + " is not a trap handler"); return false; } // Build a list of Units, other than head itself, which, if // they threw a caught exception, would cause // CompleteUnitGraph to add an edge from head to the tail: List possibleThrowers = new ArrayList(exceptional.getSuccsOf(head)); possibleThrowers.remove(tail); // This method wouldn't have been possibleThrowers.remove(head); // called if tail was not in // g.getSuccsOf(head). // We need to screen out Traps that catch exceptions from // head itself, since they should be in both CompleteUnitGraph // and TrapUnitGraph. List headsCatchers = new ArrayList(); for (Iterator it = exceptional.getExceptionDests(head).iterator(); it.hasNext(); ) { ExceptionDest dest = (ExceptionDest) it.next(); headsCatchers.add(dest.getTrap()); } // Now ensure that one of the possibleThrowers might throw // an exception caught by one of tail's Traps, // screening out combinations where possibleThrower is the // first trapped Unit, if screenFirstTrappedUnit is true. for (Iterator throwerIt = possibleThrowers.iterator(); throwerIt.hasNext(); ) { Unit thrower = (Unit) throwerIt.next(); Collection dests = exceptional.getExceptionDests(thrower); for (Iterator destIt = dests.iterator(); destIt.hasNext(); ) { ExceptionDest dest = (ExceptionDest) destIt.next(); Trap trap = dest.getTrap(); if (tailsTraps.contains(trap)) { if (headsCatchers.contains(trap)) { throw new RuntimeException("trapsReachedViaEdge(): somehow there is no TrapUnitGraph edge from " + head + " to " + tail + " even though the former throws an exception caught by the latter!"); } else if ((! predOfTrappedThrowerScreensFirstTrappedUnit) || (thrower != trap.getBeginUnit())) { return true; } } } } return false; } /** * Utility method that returns all the {@link Trap}s whose * handlers begin with a particular {@link Unit}. * * @param handler the handler {@link Unit} whose {@link Trap}s are * to be returned. * @return a {@link List} of {@link Trap}s with <code>handler</code> * as their handler's first {@link Unit}. The list may be empty. */ protected List returnHandlersTraps(Unit handler) { Body body = exceptional.getBody(); List result = null; for (Iterator it = body.getTraps().iterator(); it.hasNext(); ) { Trap trap = (Trap) it.next(); if (trap.getHandlerUnit() == handler) { if (result == null) { result = new ArrayList(); } result.add(trap); } } if (result == null) { result = Collections.EMPTY_LIST; } return result; } } /** * Class for comparing an {@link ExceptionalUnitGraph} to a {@link * ClassicCompleteUnitGraph} . Since {@link * ClassicCompleteUnitGraph} is a subclass of {@link * TrapUnitGraph}, this class makes only minor adjustments to its * parent. */ class ExceptionalToClassicCompleteUnitGraphComparer extends ExceptionalToTrapUnitGraphComparer { ExceptionalToClassicCompleteUnitGraphComparer(ExceptionalUnitGraph exceptional, ClassicCompleteUnitGraph trap, boolean omitExceptingUnitEdges) { super(exceptional, trap, omitExceptingUnitEdges); this.predOfTrappedThrowerScreensFirstTrappedUnit = true; } protected boolean cannotReallyThrowTo(Unit head, Unit tail) { if (Options.v().verbose()) G.v().out.println("ExceptionalToClassicCompleteUnitGraphComparer.cannotReallyThrowTo() called."); if (super.cannotReallyThrowTo(head, tail)) { return true; } else { // A ClassicCompleteUnitGraph will consider the predecessors // of the first trapped Unit as predecessors of the Trap's // handler. List headsSuccs = exceptional.getSuccsOf(head); List tailsTraps = returnHandlersTraps(tail); for (Iterator it = tailsTraps.iterator(); it.hasNext(); ) { Trap tailsTrap = (Trap) it.next(); Unit tailsFirstTrappedUnit = tailsTrap.getBeginUnit(); if (headsSuccs.contains(tailsFirstTrappedUnit)) { Collection succsDests = exceptional.getExceptionDests(tailsFirstTrappedUnit); if ((! destCollectionIncludes(succsDests, tailsTrap)) || (! CompleteUnitGraph.mightHaveSideEffects(tailsFirstTrappedUnit))) { return true; } } } return false; } } } /** * Class for Comparing a {@link BriefBlockGraph}, {@link * ArrayRefBlockGraph}, or {@link ZonedBlockGraph} to an {@link * AltBriefBlockGraph}, {@link AltArrayRefBlockGraph}, or {@link * AltZonedBlockGraph}, respectively. The two should differ only * in that the <tt>Alt</tt> variants have empty tail lists. */ class BlockToAltBlockGraphComparer implements TypedGraphComparer { DirectedGraph reg; // The regular BlockGraph. DirectedGraph alt; // The Alt BlockGraph. BlockToAltBlockGraphComparer(DirectedGraph reg, DirectedGraph alt) { this.reg = reg; this.alt = alt; } public boolean onlyExpectedDiffs() { if (reg.size() != alt.size()) { return false; } if (! equivLists(reg.getHeads(), alt.getHeads())) { return false; } if (alt.getTails().size() != 0) { return false; } return equivPredsAndSuccs(); } } /** * A utility method for determining if a {@link Collection} * of {@link CompleteUnitGraph#ExceptionDest} contains * one which leads to a specified {@link Trap}. * * @param dests the {@link Collection} of {@link * CompleteUnitGraph#ExceptionDest}s to search. * * @param trap the {@link Trap} to search for. * * @return <tt>true</tt> if <tt>dests</tt> contains * <tt>trap</tt> as a destination, false otherwise. */ private static boolean destCollectionIncludes(Collection dests, Trap trap) { for (Iterator destIt = dests.iterator(); destIt.hasNext(); ) { ExceptionDest dest = (ExceptionDest) destIt.next(); if (dest.getTrap() == trap) { return true; } } return false; } /** * Utility method to return the {@link Body} associated with a * {@link DirectedGraph}, if there is one. * * @param g the graph for which to return a {@link Body}. * * @return the {@link Body} represented by <tt>g</tt>, if <tt>g</tt> * is a control flow graph, or <tt>null</tt> if <tt>g</tt> is not a * control flow graph. */ private static Body getGraphsBody(DirectedGraph g) { Body result = null; if (g instanceof UnitGraph) { result = ((UnitGraph) g).getBody(); } else if (g instanceof BlockGraph) { result = ((BlockGraph) g).getBody(); } return result; } /** * Utility method that returns a {@link LabeledUnitPrinter} for printing * the {@link Unit}s in graph. * * @param g the graph for which to return a {@link LabeledUnitPrinter}. * @return A {@link LabeledUnitPrinter} for printing the {@link Unit}s in * <tt>g</tt> if <tt>g</tt> is a control flow graph. Returns * <tt>null</tt> if <tt>g</tt> is not a control flow graph. */ private static LabeledUnitPrinter makeUnitPrinter(DirectedGraph g) { Body b = getGraphsBody(g); if (b == null) { return null; } else { BriefUnitPrinter printer = new BriefUnitPrinter(b); printer.noIndent(); return printer; } } /** * Utility method to return a {@link String} representation of a * graph node. * * @param node an {@link Object} representing a node in a * {@link DirectedGraph}. * * @param printer either a {@link LabeledUnitPrinter} for printing the * {@link Unit}s in the graph represented by <tt>node</tt>'s * graph, if node is part of a control flow graph, or <tt>null</tt>, if * <tt>node</tt> is not part of a control flow graph. * * @return a {@link String} representation of <tt>node</tt>. */ private static String nodeToString(Object node, LabeledUnitPrinter printer) { String result = null; if (printer == null) { result = node.toString(); } else if (node instanceof Unit) { ((Unit) node).toString(printer); result = printer.toString(); } else if (node instanceof Block) { StringBuffer buffer = new StringBuffer(); Iterator units = ((Block) node).iterator(); while (units.hasNext()) { Unit unit = (Unit) units.next(); String targetLabel = (String) printer.labels().get(unit); if (targetLabel != null) { buffer.append(targetLabel) .append(": "); } unit.toString(printer); buffer.append(printer.toString()).append("; "); } result = buffer.toString(); } return result; } /** * A utility method for reporting the differences between two lists * of graph nodes. The lists are usually the result of calling * <tt>getHeads()</tt>, <tt>getTails()</tt>, <tt>getSuccsOf()</tt> * or <tt>getPredsOf()</tt> on each of two graphs being compared. * * @param buffer a {@link StringBuffer} to which to append the * description of differences. * * @param printer1 a {@link LabeledUnitPrinter} to be used to format * any {@link Unit}s found in <tt>list1</tt>. * * @param printer2 a {@link LabeledUnitPrinter} to be used to format * any {@link Unit}s found in <tt>list2</tt>. * * @param label a string characterizing these lists. * * @param list1 the list from the first graph, or <tt>null</tt> if * this list is missing in the first graph. * * @param list2 the list from the second graph, or <tt>null</tt> if * this list is missing in the second graph. */ private void diffList(StringBuffer buffer, LabeledUnitPrinter printer1, LabeledUnitPrinter printer2, String label, List list1, List list2) { if (! equivLists(list1, list2)) { buffer.append("*********\n"); if (list1 == null) { buffer.append("+ "); list1 = Collections.EMPTY_LIST; } else if (list2 == null) { buffer.append("- "); list2 = Collections.EMPTY_LIST; } else { buffer.append(" "); } buffer.append(label) .append(":\n"); for (Iterator it = list1.iterator(); it.hasNext(); ) { Object list1Node = it.next(); Object list2Node = equivalences.getEquiv(list1Node); if (list2.contains(list2Node)) { buffer.append(" "); } else { buffer.append("- "); } buffer.append(nodeToString(list1Node, printer1)).append("\n"); } for (Iterator it = list2.iterator(); it.hasNext(); ) { Object list2Node = it.next(); Object list1Node = equivalences.getEquiv(list2Node); if (! list1.contains(list1Node)) { buffer.append("+ ") .append(nodeToString(list2Node,printer2)) .append("\n"); } } buffer.append("---------\n"); } } /** * Utility method that determines if two lists of nodes are equivalent. * * @param list1 The first list of nodes. * @param list2 The second list of nodes. * @return <tt>true</tt> if the equivalent of each node in <tt>list1</tt> * is found in <tt>list2</tt>, and vice versa. */ private boolean equivLists(List list1, List list2) { if (list1 == null) { return (list2 == null); } else if (list2 == null) { return false; } if (list1.size() != list2.size()) { return false; } for (Iterator i = list1.iterator(); i.hasNext(); ) { if (! list2.contains(equivalences.getEquiv(i.next()))) { return false; } } // Since getEquiv() should be symmetric, and we've confirmed that // the lists are the same size, the next loop shouldn't really // be necessary. But just in case something is fouled up, we // include this loop as an assertion check. for (Iterator i = list2.iterator(); i.hasNext(); ) { if (! list1.contains(equivalences.getEquiv(i.next()))) { throw new IllegalArgumentException("equivLists: " + list2.toString() + " contains all the equivalents of " + list1.toString() + ", but the reverse is not true."); } } return true; } }