package soot.jimple.toolkits.ide.icfg;
import heros.DontSynchronize;
import heros.InterproceduralCFG;
import heros.SynchronizedBy;
import heros.ThreadSafe;
import heros.solver.IDESolver;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import soot.Body;
import soot.MethodOrMethodContext;
import soot.PatchingChain;
import soot.Scene;
import soot.SootMethod;
import soot.Unit;
import soot.UnitBox;
import soot.jimple.Stmt;
import soot.jimple.toolkits.callgraph.CallGraph;
import soot.jimple.toolkits.callgraph.Edge;
import soot.jimple.toolkits.callgraph.EdgePredicate;
import soot.jimple.toolkits.callgraph.Filter;
import soot.jimple.toolkits.callgraph.ReachableMethods;
import soot.toolkits.exceptions.UnitThrowAnalysis;
import soot.toolkits.graph.DirectedGraph;
import soot.toolkits.graph.ExceptionalUnitGraph;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
/**
* Default implementation for the {@link InterproceduralCFG} interface.
* Includes all statements reachable from {@link Scene#getEntryPoints()} through
* explicit call statements or through calls to {@link Thread#start()}.
*
* This class is designed to be thread safe, and subclasses of this class must be designed
* in a thread-safe way, too.
*/
@ThreadSafe
public class JimpleBasedInterproceduralCFG implements InterproceduralCFG<Unit,SootMethod> {
//retains only callers that are explicit call sites or Thread.start()
protected static class EdgeFilter extends Filter {
protected EdgeFilter() {
super(new EdgePredicate() {
public boolean want(Edge e) {
return e.kind().isExplicit() || e.kind().isThread();
}
});
}
}
@DontSynchronize("readonly")
protected final CallGraph cg;
@DontSynchronize("written by single thread; read afterwards")
protected final Map<Unit,Body> unitToOwner = new HashMap<Unit,Body>();
@SynchronizedBy("by use of synchronized LoadingCache class")
protected final LoadingCache<Body,DirectedGraph<Unit>> bodyToUnitGraph =
IDESolver.DEFAULT_CACHE_BUILDER.build( new CacheLoader<Body,DirectedGraph<Unit>>() {
public DirectedGraph<Unit> load(Body body) throws Exception {
return makeGraph(body);
}
});
@SynchronizedBy("by use of synchronized LoadingCache class")
protected final LoadingCache<Unit,Set<SootMethod>> unitToCallees =
IDESolver.DEFAULT_CACHE_BUILDER.build( new CacheLoader<Unit,Set<SootMethod>>() {
public Set<SootMethod> load(Unit u) throws Exception {
Set<SootMethod> res = new LinkedHashSet<SootMethod>();
//only retain callers that are explicit call sites or Thread.start()
Iterator<Edge> edgeIter = new EdgeFilter().wrap(cg.edgesOutOf(u));
while(edgeIter.hasNext()) {
Edge edge = edgeIter.next();
if(edge.getTgt()==null) {
System.err.println();
}
SootMethod m = edge.getTgt().method();
if(m.hasActiveBody())
res.add(m);
}
return res;
}
});
@SynchronizedBy("by use of synchronized LoadingCache class")
protected final LoadingCache<SootMethod,Set<Unit>> methodToCallers =
IDESolver.DEFAULT_CACHE_BUILDER.build( new CacheLoader<SootMethod,Set<Unit>>() {
public Set<Unit> load(SootMethod m) throws Exception {
Set<Unit> res = new LinkedHashSet<Unit>();
//only retain callers that are explicit call sites or Thread.start()
Iterator<Edge> edgeIter = new EdgeFilter().wrap(cg.edgesInto(m));
while(edgeIter.hasNext()) {
Edge edge = edgeIter.next();
res.add(edge.srcUnit());
}
return res;
}
});
@SynchronizedBy("by use of synchronized LoadingCache class")
protected final LoadingCache<SootMethod,Set<Unit>> methodToCallsFromWithin =
IDESolver.DEFAULT_CACHE_BUILDER.build( new CacheLoader<SootMethod,Set<Unit>>() {
public Set<Unit> load(SootMethod m) throws Exception {
Set<Unit> res = new LinkedHashSet<Unit>();
//only retain calls that are explicit call sites or Thread.start()
Iterator<Edge> edgeIter = new EdgeFilter().wrap(cg.edgesOutOf(m));
while(edgeIter.hasNext()) {
Edge edge = edgeIter.next();
res.add(edge.srcUnit());
}
return res;
}
});
public JimpleBasedInterproceduralCFG() {
cg = Scene.v().getCallGraph();
List<MethodOrMethodContext> eps = new ArrayList<MethodOrMethodContext>();
eps.addAll(Scene.v().getEntryPoints());
ReachableMethods reachableMethods = new ReachableMethods(cg, eps.iterator(), new EdgeFilter());
reachableMethods.update();
for(Iterator<MethodOrMethodContext> iter = reachableMethods.listener(); iter.hasNext(); ) {
SootMethod m = iter.next().method();
if(m.hasActiveBody()) {
Body b = m.getActiveBody();
PatchingChain<Unit> units = b.getUnits();
for (Unit unit : units) {
unitToOwner.put(unit, b);
}
}
}
}
@Override
public SootMethod getMethodOf(Unit u) {
return unitToOwner.get(u).getMethod();
}
@Override
public List<Unit> getSuccsOf(Unit u) {
Body body = unitToOwner.get(u);
DirectedGraph<Unit> unitGraph = getOrCreateUnitGraph(body);
return unitGraph.getSuccsOf(u);
}
private DirectedGraph<Unit> getOrCreateUnitGraph(Body body) {
return bodyToUnitGraph.getUnchecked(body);
}
protected synchronized DirectedGraph<Unit> makeGraph(Body body) {
return new ExceptionalUnitGraph(body, UnitThrowAnalysis.v() ,true);
}
@Override
public Set<SootMethod> getCalleesOfCallAt(Unit u) {
return unitToCallees.getUnchecked(u);
}
@Override
public List<Unit> getReturnSitesOfCallAt(Unit u) {
return getSuccsOf(u);
}
@Override
public boolean isCallStmt(Unit u) {
return ((Stmt)u).containsInvokeExpr();
}
@Override
public boolean isExitStmt(Unit u) {
Body body = unitToOwner.get(u);
DirectedGraph<Unit> unitGraph = getOrCreateUnitGraph(body);
return unitGraph.getTails().contains(u);
}
@Override
public Set<Unit> getCallersOf(SootMethod m) {
return methodToCallers.getUnchecked(m);
}
@Override
public Set<Unit> getCallsFromWithin(SootMethod m) {
return methodToCallsFromWithin.getUnchecked(m);
}
@Override
public Set<Unit> getStartPointsOf(SootMethod m) {
if(m.hasActiveBody()) {
Body body = m.getActiveBody();
DirectedGraph<Unit> unitGraph = getOrCreateUnitGraph(body);
return new LinkedHashSet<Unit>(unitGraph.getHeads());
}
return null;
}
@Override
public boolean isStartPoint(Unit u) {
Body body = unitToOwner.get(u);
DirectedGraph<Unit> unitGraph = getOrCreateUnitGraph(body);
return unitGraph.getHeads().contains(u);
}
@Override
//TODO do we need to replace call by return for backwards analysis?
public Set<Unit> allNonCallStartNodes() {
Set<Unit> res = new LinkedHashSet<Unit>(unitToOwner.keySet());
for (Iterator<Unit> iter = res.iterator(); iter.hasNext();) {
Unit u = iter.next();
if(isStartPoint(u) || isCallStmt(u)) iter.remove();
}
return res;
}
@Override
public boolean isFallThroughSuccessor(Unit u, Unit succ) {
assert getSuccsOf(u).contains(succ);
if(!u.fallsThrough()) return false;
Body body = unitToOwner.get(u);
return body.getUnits().getSuccOf(u) == succ;
}
@Override
public boolean isBranchTarget(Unit u, Unit succ) {
assert getSuccsOf(u).contains(succ);
if(!u.branches()) return false;
for (UnitBox ub : succ.getUnitBoxes()) {
if(ub.getUnit()==succ) return true;
}
return false;
}
}