/* Soot - a J*va Optimization Framework * Copyright (C) 1999-2010 Hossein Sadat-Mohtasham * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ package soot.toolkits.graph.pdg; import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import soot.Body; import soot.Trap; import soot.Unit; import soot.jimple.ThrowStmt; import soot.jimple.internal.JNopStmt; import soot.toolkits.graph.DominatorNode; import soot.toolkits.graph.MHGDominatorsFinder; import soot.toolkits.graph.MHGPostDominatorsFinder; import soot.toolkits.graph.UnitGraph; import soot.util.Chain; /** * * This class represents a control flow graph which behaves like an ExceptionalUnitGraph and * BriefUnitGraph when there are no exception handling construct in the method; at the presence * of such constructs, the CFG is constructed from a brief graph by addition a concise representation * of the exceptional flow as well as START/STOP auxiliary nodes. In a nutshell, the exceptional flow * is represented at the level of try-catch-finally blocks instead of the Unit level to allow a more * useful region analysis. * * @author Hossein Sadat-Mohtasham * */ public class EnhancedUnitGraph extends UnitGraph { //This keeps a map from the beginning of each guarded block //to the corresponding special EHNopStmt. //protected Hashtable<GuardedBlock, Unit> try2nop = null; protected Hashtable<Unit, Unit> try2nop = null; //Keep the real header of the handler block protected Hashtable<Unit, Unit> handler2header = null; public EnhancedUnitGraph(Body body) { super(body); //try2nop = new Hashtable<GuardedBlock, Unit>(); try2nop = new Hashtable<Unit, Unit>(); handler2header = new Hashtable<Unit, Unit>(); //there could be a maximum of traps.size() of nop //units added to the CFG plus potentially START/STOP nodes. int size = unitChain.size() + body.getTraps().size() + 2; unitToSuccs = new HashMap<Unit, List<Unit>>(size * 2 + 1, 0.7f); unitToPreds = new HashMap<Unit, List<Unit>>(size * 2 + 1, 0.7f); /* * Compute the head and tails at each phase because other phases * might rely on them. */ buildUnexceptionalEdges(unitToSuccs, unitToPreds); addAuxiliaryExceptionalEdges(); buildHeadsAndTails(); handleExplicitThrowEdges(); buildHeadsAndTails(); handleMultipleReturns(); buildHeadsAndTails(); /** * Remove bogus heads (these are useless goto's) */ removeBogusHeads(); buildHeadsAndTails(); makeMappedListsUnmodifiable(unitToSuccs); makeMappedListsUnmodifiable(unitToPreds); } /** * This method adds a STOP node to the graph, if necessary, to make the CFG * single-tailed. */ protected void handleMultipleReturns() { if(this.getTails().size() > 1) { Unit stop = new ExitStmt(); List<Unit> predsOfstop = new ArrayList<Unit>(); for(Iterator<Unit> tailItr = this.getTails().iterator(); tailItr.hasNext(); ) { Unit tail = tailItr.next(); predsOfstop.add(tail); List<Unit> tailSuccs = this.unitToSuccs.get(tail); tailSuccs.add(stop); } this.unitToPreds.put(stop, predsOfstop); this.unitToSuccs.put(stop, new ArrayList<Unit>()); Chain<Unit> units = body.getUnits().getNonPatchingChain(); if(!units.contains(stop)) units.addLast(stop); } } /** * This method removes all the heads in the CFG except the one * that corresponds to the first unit in the method. */ protected void removeBogusHeads() { Chain<Unit> units = body.getUnits(); Unit trueHead = units.getFirst(); while(this.getHeads().size() > 1) { for(Iterator<Unit> headItr = this.getHeads().iterator(); headItr.hasNext(); ) { Unit head = headItr.next(); if(trueHead == head) continue; this.unitToPreds.remove(head); List<Unit> succs = this.unitToSuccs.get(head); for(Iterator<Unit> succsItr = succs.iterator(); succsItr.hasNext(); ) { List<Unit> tobeRemoved = new ArrayList<Unit>(); Unit succ = succsItr.next(); List<Unit> predOfSuccs = this.unitToPreds.get(succ); for(Iterator<Unit> predItr = predOfSuccs.iterator(); predItr.hasNext(); ) { Unit pred = predItr.next(); if(pred == head) tobeRemoved.add(pred); } predOfSuccs.removeAll(tobeRemoved); } this.unitToSuccs.remove(head); if(units.contains(head)) units.remove(head); } this.buildHeadsAndTails(); } } @SuppressWarnings("unchecked") protected void handleExplicitThrowEdges() { MHGDominatorTree dom = new MHGDominatorTree(new MHGDominatorsFinder<Unit>(this)); MHGDominatorTree pdom = new MHGDominatorTree(new MHGPostDominatorsFinder(this)); //this keeps a map from the entry of a try-catch-block to a selected merge point Hashtable<Unit, Unit> x2mergePoint = new Hashtable<Unit, Unit>(); List<Unit> tails = this.getTails(); TailsLoop: for(Iterator<Unit> itr = tails.iterator(); itr.hasNext(); ) { Unit tail = itr.next(); if(!(tail instanceof ThrowStmt)) continue; DominatorNode x = dom.getDode(tail); DominatorNode parentOfX = dom.getParentOf(x); Object xgode = x.getGode(); DominatorNode xpdomDode = pdom.getDode(xgode); Object parentXGode = parentOfX.getGode(); DominatorNode parentpdomDode = pdom.getDode(parentXGode); //while x post-dominates its dominator (parent in dom) while(pdom.isDominatorOf(xpdomDode, parentpdomDode)) { x = parentOfX; parentOfX = dom.getParentOf(x); //If parent is null we must be at the head of the graph if(parentOfX == null) //throw new RuntimeException("This should never have happened!"); break; xgode = x.getGode(); xpdomDode = pdom.getDode(xgode); parentXGode = parentOfX.getGode(); parentpdomDode = pdom.getDode(parentXGode); } if(parentOfX != null) x = parentOfX; xgode = x.getGode(); xpdomDode = pdom.getDode(xgode); Unit mergePoint = null; if(x2mergePoint.containsKey(xgode)) mergePoint = x2mergePoint.get(xgode); else { //Now get all the children of x in the dom List<DominatorNode> domChilds = dom.getChildrenOf(x); Object child1god = null; Object child2god = null; for(Iterator<DominatorNode> domItr = domChilds.iterator(); domItr.hasNext(); ) { DominatorNode child = domItr.next(); Object childGode = child.getGode(); DominatorNode childpdomDode = pdom.getDode(childGode); //we don't want to make a loop! List<Unit> path = this.getExtendedBasicBlockPathBetween((Unit)childGode, tail); //if(dom.isDominatorOf(child, dom.getDode(tail))) if(!(path == null || path.size() == 0)) continue; if(pdom.isDominatorOf(childpdomDode, xpdomDode)) { mergePoint = (Unit) child.getGode(); break; } //gather two eligible childs if(child1god == null) child1god = childGode; else if(child2god == null) child2god = childGode; } if(mergePoint == null) { if(child1god != null && child2god != null) { DominatorNode child1 = pdom.getDode(child1god); DominatorNode child2 = pdom.getDode(child2god); //go up the pdom tree and find the common parent of child1 and child2 DominatorNode comParent = child1.getParent(); while(comParent != null) { if(pdom.isDominatorOf(comParent, child2)) { mergePoint = (Unit) comParent.getGode(); break; } comParent = comParent.getParent(); } } else if(child1god != null || child2god != null){ DominatorNode y = null; if(child1god != null) y = pdom.getDode(child1god); else if(child2god != null) y = pdom.getDode(child2god); DominatorNode initialY = dom.getDode(y.getGode()); DominatorNode yDodeInDom = initialY; while(dom.isDominatorOf(x, yDodeInDom)) { y = y.getParent(); //If this is a case where the childs of a conditional //are all throws, or returns, just forget it! if(y == null) { break ; } yDodeInDom = dom.getDode(y.getGode()); } if(y != null) mergePoint = (Unit) y.getGode(); else mergePoint = (Unit) initialY.getGode(); } } //This means no (dom) child of x post-dominates x, so just use the child that is //immediately /*if(mergePoint == null) { //throw new RuntimeException("No child post-dominates x."); mergePoint = potentialMergePoint; }*/ //This means no (dom) child of x post-dominates x, so just use the child that is //immediately. this means there is no good reliable merge point. So we just fetch the succ //of x in CFg so that the succ does not dominate the throw, and find the first //post-dom of the succ so that x does not dom it. // if(mergePoint == null) { List<Unit> xSucc = this.unitToSuccs.get(x.getGode()); for(Iterator<Unit> uItr = xSucc.iterator(); uItr.hasNext(); ) { Unit u = uItr.next(); if(dom.isDominatorOf(dom.getDode(u), dom.getDode(tail))) continue; DominatorNode y = pdom.getDode(u); while(dom.isDominatorOf(x, y)) { y = y.getParent(); //If this is a case where the childs of a conditional //are all throws, or returns, just forget it! if(y == null) { continue TailsLoop; } } mergePoint = (Unit) y.getGode(); break; } } //the following happens if the throw is the only exit in the method (even if return stmt is present.) else if(dom.isDominatorOf(dom.getDode(mergePoint), dom.getDode(tail))) continue TailsLoop; if(mergePoint == null) throw new RuntimeException("This should not have happened!"); x2mergePoint.put((Unit) xgode, mergePoint); } //add an edge from the tail (throw) to the merge point if(!this.unitToSuccs.containsKey(tail)) this.unitToSuccs.put(tail, new ArrayList<Unit>()); List<Unit> throwSuccs = this.unitToSuccs.get(tail); throwSuccs.add(mergePoint); List<Unit> mergePreds = this.unitToPreds.get(mergePoint); mergePreds.add(tail); } } /** * Add an exceptional flow edge for each handler from the corresponding * auxiliary nop node to the beginning of the handler. */ protected void addAuxiliaryExceptionalEdges() { //Do some preparation for each trap in the method for (Iterator<Trap> trapIt = body.getTraps().iterator(); trapIt.hasNext(); ) { Trap trap = trapIt.next(); /** * Find the real header of this handler block * */ Unit handler = trap.getHandlerUnit(); Unit pred = handler; while(this.unitToPreds.get(pred).size() > 0) pred = this.unitToPreds.get(pred).get(0); handler2header.put(handler, pred); /***********/ /* * Keep this here for possible future changes. */ /*GuardedBlock gb = new GuardedBlock(trap.getBeginUnit(), trap.getEndUnit()); Unit ehnop; if(try2nop.containsKey(gb)) ehnop = try2nop.get(gb); else { ehnop = new EHNopStmt(); try2nop.put(gb, ehnop); }*/ Unit ehnop; if(try2nop.containsKey(trap.getBeginUnit())) ehnop = try2nop.get(trap.getBeginUnit()); else { ehnop = new EHNopStmt(); try2nop.put(trap.getBeginUnit(), ehnop); } } //Only add a nop once Hashtable<Unit, Boolean> nop2added = new Hashtable<Unit, Boolean>(); // Now actually add the edge AddExceptionalEdge: for (Iterator<Trap> trapIt = body.getTraps().iterator(); trapIt.hasNext(); ) { Trap trap = trapIt.next(); Unit b = trap.getBeginUnit(); Unit handler = trap.getHandlerUnit(); handler = handler2header.get(handler); /** * Check if this trap is a finally trap that handles exceptions of an adjacent catch block; * what differentiates such trap is that it's guarded region has the same parent as the * handler of the trap itself, in the dom tree. * * The problem is that we don't have a complete DOM tree at this transient state. * * The work-around is to not process a trap that has already an edge pointing to it. * */ if(this.unitToPreds.containsKey(handler)) { List<Unit> handlerPreds = this.unitToPreds.get(handler); for(Iterator<Unit> preditr = handlerPreds.iterator(); preditr.hasNext(); ) if(try2nop.containsValue(preditr.next())) continue AddExceptionalEdge; } else continue; //GuardedBlock gb = new GuardedBlock(b, e); Unit ehnop = try2nop.get(b); if(!nop2added.containsKey(ehnop)) { List<Unit> predsOfB = getPredsOf(b); List<Unit> predsOfehnop = new ArrayList<Unit>(predsOfB); for(Iterator<Unit> itr = predsOfB.iterator(); itr.hasNext(); ) { Unit a = itr.next(); List<Unit> succsOfA = this.unitToSuccs.get(a); succsOfA.remove(b); succsOfA.add((Unit)ehnop); } predsOfB.clear(); predsOfB.add((Unit)ehnop); this.unitToPreds.put((Unit)ehnop, predsOfehnop); } if(!this.unitToSuccs.containsKey(ehnop)) this.unitToSuccs.put(ehnop, new ArrayList<Unit>()); List<Unit> succsOfehnop = this.unitToSuccs.get(ehnop); if(!succsOfehnop.contains(b)) succsOfehnop.add(b); succsOfehnop.add(handler); if(!this.unitToPreds.containsKey(handler)) this.unitToPreds.put(handler, new ArrayList<Unit>()); List<Unit> predsOfhandler = this.unitToPreds.get(handler); predsOfhandler.add((Unit)ehnop); Chain<Unit> units = body.getUnits().getNonPatchingChain(); if(!units.contains(ehnop)) units.insertBefore((Unit)ehnop, b); nop2added.put(ehnop, Boolean.TRUE); } } } /** * This class represents a block of code guarded by a trap. Currently, this * is not used but it might well be put to use in later updates. * * @author Hossein Sadat-Mohtasham * */ class GuardedBlock { Unit start, end; public GuardedBlock(Unit s, Unit e) { this.start = s; this.end = e; } public int hashCode() { // Following Joshua Bloch's recipe in "Effective Java", Item 8: int result = 17; result = 37 * result + this.start.hashCode(); result = 37 * result + this.end.hashCode(); return result; } public boolean equals(Object rhs) { if (rhs == this) { return true; } if (! (rhs instanceof GuardedBlock)) { return false; } GuardedBlock rhsGB = (GuardedBlock) rhs; return ((this.start == rhsGB.start) && (this.end == rhsGB.end)); } } /** * * @author Hossein Sadat-Mohtasham * Feb 2010 * * This class represents a special nop statement that marks the * beginning of a try block at the Jimple level. This is going * to be used in the CFG enhancement. * */ @SuppressWarnings("serial") class EHNopStmt extends JNopStmt { public EHNopStmt() { } public Object clone() { return new EHNopStmt(); } public boolean fallsThrough(){return true;} public boolean branches(){return false;} } /** * * @author Hossein Sadat-Mohtasham * Feb 2010 * * This class represents a special nop statement that marks the * beginning of method body at the Jimple level. This is going * to be used in the CFG enhancement. * */ @SuppressWarnings("serial") class EntryStmt extends JNopStmt { public EntryStmt() { } public Object clone() { return new EntryStmt(); } public boolean fallsThrough(){return true;} public boolean branches(){return false;} } /** * * @author Hossein Sadat-Mohtasham * Feb 2010 * * This class represents a special nop statement that marks the * exit of method body at the Jimple level. This is going * to be used in the CFG enhancement. * */ @SuppressWarnings("serial") class ExitStmt extends JNopStmt { public ExitStmt() { } public Object clone() { return new ExitStmt(); } public boolean fallsThrough(){return true;} public boolean branches(){return false;} }