/** * */ package soottocfg.cfg.util; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Map; import java.util.Queue; import java.util.Set; import org.jgrapht.DirectedGraph; import org.jgrapht.Graphs; import org.jgrapht.graph.DefaultDirectedGraph; import org.jgrapht.graph.DefaultEdge; import com.google.common.base.Optional; import com.google.common.base.Verify; import soottocfg.cfg.method.CfgBlock; import soottocfg.cfg.method.CfgEdge; import soottocfg.cfg.method.Method; import soottocfg.cfg.statement.CallStatement; import soottocfg.cfg.statement.PullStatement; import soottocfg.cfg.statement.PushStatement; import soottocfg.cfg.statement.Statement; import soottocfg.cfg.variable.ClassVariable; import soottocfg.soot.SootToCfg; import soottocfg.soot.util.FlowBasedPointsToAnalysis; import soottocfg.util.Pair; /** * @author schaef * */ public class InterProceduralPullPushOrdering { private Map<Method, Pair<FixedPointObject, FixedPointObject>> methodEntryExit; static class FixedPointObject { public Optional<Statement> stmt = Optional.absent(); public Method containingMethod = null; // public CfgBlock containingCfgBlock = null; // public final Set<PushStatement> pushes = new HashSet<PushStatement>(); @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("FPNode: "); sb.append(stmt); sb.append(" in "); sb.append(this.containingMethod); return sb.toString(); } } private Map<PullStatement, FixedPointObject> pullMap = new HashMap<PullStatement, FixedPointObject>(); private final DirectedGraph<FixedPointObject, DefaultEdge> ipgraph; public InterProceduralPullPushOrdering(Method entryPoint) { methodEntryExit = new HashMap<Method, Pair<FixedPointObject, FixedPointObject>>(); ipgraph = new DefaultDirectedGraph<FixedPointObject, DefaultEdge>(DefaultEdge.class); buildInterProcGraph(entryPoint, ipgraph); for (FixedPointObject fpo : ipgraph.vertexSet()) { if (fpo.stmt.isPresent() && fpo.stmt.get() instanceof PullStatement) { PullStatement pull = (PullStatement) fpo.stmt.get(); Verify.verify(!pullMap.containsKey(pull), pull.toString() + " already in there. " + fpo.containingMethod); pullMap.put(pull, fpo); } } } public void debugPrintInfluencingPushs(PullStatement pull) { StringBuilder sb = new StringBuilder(); sb.append("Listing pushs that may influence:\n "); sb.append(pull); sb.append("\n"); for (FixedPointObject fpo : getFPOsInfluencing(pull)) { sb.append("\t"); sb.append(fpo.stmt); sb.append("\tin Method"); if (fpo.containingMethod != null) { sb.append(fpo.containingMethod.getMethodName()); } sb.append("\n"); } System.err.println(sb.toString()); } /** * TODO: rewrite! * For the lack of a better points-to analysis, this checks for every pull * if there are any previous pushs to a subtype * of the object being pulled. We use this to decide which possible * invariants we have to look for. * As the name suggests, this is only a temporary solution and should be * replaced by something more efficient later. * * @param pull * @return */ public Set<ClassVariable> getBrutalOverapproximationOfPossibleType(PullStatement pull) { Set<ClassVariable> usedSubtypes = new HashSet<ClassVariable>(); usedSubtypes.add(pull.getClassSignature()); if (!pullMap.containsKey(pull)) { // System.err.println("Pull not reachable from program entry: " + pull); return usedSubtypes; } FixedPointObject fpo = pullMap.get(pull); Queue<FixedPointObject> todo = new LinkedList<FixedPointObject>(); todo.addAll(Graphs.predecessorListOf(ipgraph, fpo)); Set<FixedPointObject> done = new HashSet<FixedPointObject>(); while (!todo.isEmpty()) { FixedPointObject cur = todo.remove(); done.add(cur); if (cur.stmt.isPresent() && cur.stmt.get() instanceof PushStatement && (((PushStatement) cur.stmt.get()).getClassSignature().subclassOf(pull.getClassSignature()))) { usedSubtypes.add(((PushStatement) cur.stmt.get()).getClassSignature()); } for (FixedPointObject pre : Graphs.predecessorListOf(ipgraph, cur)) { if (!todo.contains(pre) && !done.contains(pre)) { todo.add(pre); } } } return usedSubtypes; } public Set<PushStatement> getPushsInfluencing(PullStatement pull) { Set<PushStatement> ret = new HashSet<PushStatement>(); Set<FixedPointObject> fpos = getFPOsInfluencing(pull); for (FixedPointObject fpo : fpos) { if (fpo.stmt.isPresent() && fpo.stmt.get() instanceof PushStatement) { //TODO: I guess we can assert that this is reachable. ret.add((PushStatement)fpo.stmt.get()); } } return ret; } private Set<FixedPointObject> getFPOsInfluencing(PullStatement pull) { Set<FixedPointObject> ret = new HashSet<FixedPointObject>(); if (!pullMap.containsKey(pull)) { // System.err.println("Pull not reachable from program entry: " + pull); return ret; } FixedPointObject fpo = pullMap.get(pull); Queue<FixedPointObject> todo = new LinkedList<FixedPointObject>(); todo.addAll(Graphs.predecessorListOf(ipgraph, fpo)); Set<FixedPointObject> done = new HashSet<FixedPointObject>(); while (!todo.isEmpty()) { FixedPointObject cur = todo.remove(); done.add(cur); boolean stopTraverse = false; if (cur.stmt.isPresent() && cur.stmt.get() instanceof PushStatement) { PushStatement push = (PushStatement) cur.stmt.get(); if (mayAlias(push, pull)) { ret.add(cur); // System.out.println("Must shadow " + push + " -> " + pull + " ?"); if (mustShadow(push, pull)) { // System.out.println("Yes!"); stopTraverse = true; } } } if (!stopTraverse) { for (FixedPointObject pre : Graphs.predecessorListOf(ipgraph, cur)) { if (!todo.contains(pre) && !done.contains(pre)) { todo.add(pre); } } } } // Verify.verify(!ret.isEmpty(), // "Cannot find a push that affects this pull. This would introduce an assume(false): " + pull); return ret; } /** * Returns true if the objects in the push and pull statement may be aliased. * Current we approximate this by simply checking if their types are compatible. * I.e., this is a heavy over-approximation (i.e., never returns false if alias * is not possible, but returns true in cases where no alias is possible). * @param push * @param pull * @return true if the objects in pull and push may alias. */ protected boolean mayAlias(PushStatement push, PullStatement pull) { FlowBasedPointsToAnalysis pta = SootToCfg.getPointsToAnalysis(); if (pta != null) return pta.mayAlias(pull.getObject(), push.getObject()); return canAffectPull(push, pull); } /** * Returns true if the push must shadow all preceding pushs that could * influence the pull statement. E.g., * * push(Object, o, 3) * p=o * push(Object, p, 42) * pull(Object, o) * * In this example, the second push shadows the first because any execution of pull * must go through the second push which overwrites the changes performed by the first * push. * So, mustShadow for the pull and the second push should return true, and mustShadow for the * pull and the push should return false. * * TODO: Currently, this is soundly approximated by always returning false. * @param push * @param pull * @return true if push shadows all preceding pushs that may affect pull. False otherwise. */ protected boolean mustShadow(PushStatement push, PullStatement pull) { FlowBasedPointsToAnalysis pta = SootToCfg.getPointsToAnalysis(); if (pta != null) return pta.mustAlias(pull.getObject(), push.getObject()); return false; } private boolean canAffectPull(PushStatement push, PullStatement pull) { ClassVariable pushCv = push.getClassSignature(); ClassVariable pullCv = pull.getClassSignature(); return pushCv.subclassOf(pullCv) || pushCv.superclassOf(pullCv); } private Pair<FixedPointObject, FixedPointObject> buildInterProcGraph(Method method, DirectedGraph<FixedPointObject, DefaultEdge> ipgraph) { if (methodEntryExit.containsKey(method)) { return methodEntryExit.get(method); } FixedPointObject fpEntry = new FixedPointObject(); FixedPointObject fpExit = new FixedPointObject(); ipgraph.addVertex(fpEntry); ipgraph.addVertex(fpExit); Pair<FixedPointObject, FixedPointObject> ret = new Pair<FixedPointObject, FixedPointObject>(fpEntry, fpExit); methodEntryExit.put(method, ret); Map<CfgBlock, FixedPointObject> entryFpo = new HashMap<CfgBlock, FixedPointObject>(); Map<CfgBlock, FixedPointObject> exitFpo = new HashMap<CfgBlock, FixedPointObject>(); // generate FixedPointObjects for each CfgBlock for (CfgBlock cur : method.vertexSet()) { FixedPointObject fpo; if (cur == method.getSource()) { fpo = fpEntry; } else { fpo = new FixedPointObject(); ipgraph.addVertex(fpo); } // create the subgraph from the current cfg block entryFpo.put(cur, fpo); for (Statement st : cur.getStatements()) { if (st instanceof CallStatement) { CallStatement cs = (CallStatement) st; Pair<FixedPointObject, FixedPointObject> pair = buildInterProcGraph(cs.getCallTarget(), ipgraph); ipgraph.addEdge(fpo, pair.getFirst()); fpo = new FixedPointObject(); ipgraph.addVertex(fpo); ipgraph.addEdge(pair.getSecond(), fpo); } else if (st instanceof PushStatement || st instanceof PullStatement) { fpo.stmt = Optional.of(st); // fpo.containingCfgBlock = cur; fpo.containingMethod = method; FixedPointObject nextFpo = new FixedPointObject(); ipgraph.addVertex(nextFpo); ipgraph.addEdge(fpo, nextFpo); fpo = nextFpo; } else { // do nothing } } exitFpo.put(cur, fpo); if (Graphs.successorListOf(method, cur).isEmpty()) { ipgraph.addEdge(fpo, fpExit); } } // Now connect the FixedPointObjects for (CfgEdge edge : method.edgeSet()) { FixedPointObject src = exitFpo.get(ipgraph.getEdgeSource(edge)); FixedPointObject tgt = entryFpo.get(ipgraph.getEdgeTarget(edge)); ipgraph.addEdge(src, tgt); } if (method.edgeSet().isEmpty()) { // in case the method has no body. ipgraph.addEdge(fpEntry, fpExit); } return ret; } }