package jqian.sootex.util.callgraph; import java.util.*; import jqian.Global; import jqian.sootex.AtomicTypes; import jqian.sootex.util.graph.BreathFirstSearch; import jqian.util.Utils; import soot.*; import soot.jimple.*; import soot.jimple.spark.pag.*; import soot.jimple.spark.sets.*; import soot.jimple.toolkits.callgraph.*; import soot.jimple.toolkits.pointer.DumbPointerAnalysis; import soot.toolkits.graph.*; /** * Refine call graphs, filter unnecessary edges heuristically. * Redirect the caller of Thread.start() directly to the actually stared threads' run() method * Avoid hot call graph node java.lang.Thread.run() * * Soot's CallGraphBuilder already redirect Thread.start() to Thread*.run(), but * for threads created in new Thread(Runnable) manner. The call is redirect to Thread.run(), * instead of the really started threads. * Besides, the soot2.2.3 version has a bug that when a user * overriding Thread.start(), the overrided methods are also considered as thread * starts. */ public class CallGraphRefiner{ private final SootClass THREAD_CLASS = Scene.v().getSootClass("java.lang.Thread"); private final SootClass RUNNABLE_CLASS = Scene.v().getSootClass("java.lang.Runnable"); private final SootField THREAD_TARGET_FIELD = Scene.v().getField("<java.lang.Thread: java.lang.Runnable target>"); private final SootMethod THREAD_START_METHOD = Scene.v().getMethod("<java.lang.Thread: void start()>"); private final SootMethod THREAD_RUN_METHOD = Scene.v().getMethod("<java.lang.Thread: void run()>"); private final SootMethod RUNNABLE_RUN_METHOD = Scene.v().getMethod("<java.lang.Runnable: void run()>"); //////////////////////////////////////////////////////////////////// private PointsToAnalysis _ptsTo; private boolean _verbose; private boolean _isVTA; public CallGraphRefiner(PointsToAnalysis ptsTo, boolean isVTA,boolean verbose){ this._ptsTo = ptsTo; this._verbose = verbose; this._isVTA = isVTA; } public CallGraphRefiner(PointsToAnalysis ptsTo, boolean verbose){ this(ptsTo,false,verbose); } public static class CallGraphFilter{ public Collection<SootMethod> getCallGraphEntries(){ return Scene.v().getEntryPoints(); } public boolean isEdgeIgnored(Edge edge){ return false; /*Options _options = PyxisGlobal.options(); String pkgName = cls.getPackageName(); //filter by edge types, only the implicit thread call are always kept if(kind==Kind.PRIVILEGED && _options.isPrivilegedCallIngored()){ continue; } if((kind==Kind.FINALIZE || kind==Kind.INVOKE_FINALIZE) && _options.isObjectFinalierIgnored()){ continue; } if(kind==Kind.CLINIT){ if(_options.isAllClinitIgnored()){ continue; } else if(_options.isJreClinitIgnored() && pyxis.isLibPackage(pkgName)){ continue; } } if(kind==Kind.NEWINSTANCE && _options.isReflectiveNewInstanceIgnored()){ continue; } //filter by method type //Not dig into the body of a method obviously side effect free if(pyxis.isSideEffectFreeLib(tgt) ){ continue; } //Ignore toString() methods of library class if(_options.isLibToStringIgnored() && m.getParameterCount()==0 && m.getName().equals("toString") && pyxis.isLibPackage(pkgName)){ continue; } if(_options.isNonConcreteCalleeIgnored() && !tgt.isConcrete()) continue; }*/ } } public static class AggressiveCallGraphFilter extends CallGraphRefiner.CallGraphFilter{ //XXX: Already ignore implicit entries here. // @see EntryPoints.v().implicit() public Collection<SootMethod> getCallGraphEntries(){ return EntryPoints.v().application(); } // ignore implicit calls public boolean isEdgeIgnored(Edge edge){ //XXX: We do not step into the body of a type that is considered atomic // Handling a call like Integer.intValue() takes a lot of time SootClass cls = edge.tgt().getDeclaringClass(); if(AtomicTypes.isAtomicType(cls)) return true; Kind kind = edge.kind(); if(kind.isExplicit() || kind==Kind.THREAD){ return false; } // XXX: Already ignore implicit calls here return true; } } /** No filtering, just adjust thread start call edges .*/ public CallGraph refine(CallGraph cg){ return refine(cg, new CallGraphFilter()); } /** * Refine context-insensitive call graph, heuristically ignore many methods * to reduce the analysis cost. */ public CallGraph refine(CallGraph cg, CallGraphFilter filter){ Date startTime = new Date(); int oldSize = cg.size(); Map<Unit,SootMethod> threadCall2Method = new HashMap<Unit,SootMethod>(); //selectively copy to a new call graph CallGraph newCg = new CallGraph(); Stack<SootMethod> stack = new Stack<SootMethod>(); stack.addAll(filter.getCallGraphEntries()); Set<SootMethod> processed = new HashSet<SootMethod>(); while(!stack.isEmpty()){ SootMethod m = (SootMethod)stack.pop(); if(!processed.add(m)){ continue; //if already processed } for(Iterator<Edge> it = cg.edgesOutOf(m);it.hasNext();){ Edge e = it.next(); Kind kind = e.kind(); SootMethod tgt = e.tgt(); SootClass cls = tgt.getDeclaringClass(); if(filter.isEdgeIgnored(e)){ continue; } if(kind==Kind.THREAD){ if(_verbose) Global.v().out.println(e); //XXX check if there are abnormal usage if(tgt.getName().equals("start") && !cls.getName().equals("java.lang.Thread")){ throw new RuntimeException("strange edge."); } Unit unit = e.srcUnit(); threadCall2Method.put(unit,e.src()); continue; } //remove old Thread edge, the edge will be added in later phase if(tgt.equals(THREAD_START_METHOD)){ if(_verbose) Global.v().out.println(e); continue; } newCg.addEdge(e); if(!processed.contains(tgt)){ stack.add(tgt); } } } // Add thread start relevant call edges if(_verbose){ Global.v().out.println("=================== Refined thread start call edges ========================"); } for(Map.Entry<Unit,SootMethod> entry: threadCall2Method.entrySet()){ Unit unit = entry.getKey(); SootMethod m = entry.getValue(); Collection<SootMethod> targets = resolveTargets(unit, cg); //FIXME ����Щ�����߳������ķ���ҲҪ�ռ�����Ŀ�꣬Thread+.start() -> Thread.start() //�����и�bug�����Һ�����Thread.start()����������࣬�������Ⲣ���Ǻ����� for(SootMethod tgt: targets){ Edge m2run = new Edge(m,unit,tgt,Kind.THREAD); newCg.addEdge(m2run); if(_verbose){ Global.v().out.println(" " + m2run); } } } Date endTime = new Date(); int newSize = newCg.size(); Global.v().out.println("[Call Graph] Refine call graph in " + Utils.getTimeConsumed(startTime, endTime) + ", new call graph has "+newSize +" edges (oritginal :"+ oldSize+")"); return newCg; } public void refineClinits(){ /*//call statements and their enclosing methods, only for thread relative ones Map<Unit,SootMethod> unit2Method = new HashMap(); CallGraph cg = Scene.v().getCallGraph(); CallGraph newCg = new CallGraph(); //get the edges into Thread.start() Collection edgesToThreadStart = new HashSet(); for (Iterator it = cg.edgesInto(_threadStart); it.hasNext(); ){ edgesToThreadStart.add(it.next()); } for(SootMethod entry: Scene.v().getEntryPoints()){ //collection entries Stack<SootMethod> stack = new Stack(); stack.add(entry); } //selectively copy the call graph Set processed = new HashSet(); Collection<SootClass> classes = Scene.v().getClasses(); int clsCount = classes.size()+1; int[] clsRef = new int[clsCount]; for(SootMethod m: stack){ SootClass cls = m.getDeclaringClass(); int id = cls.getNumber(); clsRef[id]++; } while(!stack.isEmpty()){ SootMethod m = (SootMethod)stack.pop(); SootClass cls = m.getDeclaringClass(); int clsId = cls.getNumber(); clsRef[clsId]--; if(!processed.add(m)){ continue; //if already processed } for(Iterator it = cg.edgesOutOf(m);it.hasNext();){ Edge e = (Edge)it.next(); Kind kind = e.kind(); SootMethod tgt = e.tgt(); cls = tgt.getDeclaringClass(); clsId = cls.getNumber(); if(kind==Kind.THREAD){ if(_verbose) PyxisGlobal.out.println(e); //XXX throw exception for unhandled thread usage. if(tgt.getName().equals("start") && !cls.getName().equals("java.lang.Thread")){ throw new RuntimeException("Unawared thread usage."); } Unit unit = e.srcUnit(); unit2Method.put(unit,e.src()); continue; } else if(kind==Kind.CLINIT){ //class already in stack, no more <clinit> call take place if(clsRef[clsId]>0){ continue; } } //remove old Thread edge, the correponding edge will be added in later phase if(edgesToThreadStart.contains(e)){ if(_verbose) System.out.println(e); continue; } //else remain newCg.addEdge(e); if(!processed.contains(tgt)){ stack.add(tgt); clsRef[clsId]++; } } } //add thread edges addThreadEdges(newCg,unit2Method); //update call graph and reachable methods Scene.v().setCallGraph(newCg); Scene.v().setReachableMethods(null); */ } /** Resolve the real targets of a method call. */ private Collection<SootMethod> resolveTargets(Unit unit, CallGraph originalCallGraph){ Collection<SootMethod> threadEntries = new LinkedList<SootMethod>(); //set of possible calling targets if(_ptsTo instanceof PAG){ InvokeStmt stmt = (InvokeStmt)unit; InstanceInvokeExpr expr = (InstanceInvokeExpr)stmt.getInvokeExpr(); Local receiver = (Local)expr.getBase(); //collect all possible receiver thread objects PointsToSet pt2set = _ptsTo.reachingObjects(receiver); Collection<AllocNode> threadObjs = new HashSet<AllocNode>(); PointsToSetInternal internalSet = (PointsToSetInternal)pt2set; P2SetVisitor visitor = new ThreadCollector(threadObjs,true,expr); internalSet.forall(visitor); //transfer thread objects to thread classes (including classes implementing Runnable) Collection<SootClass> threadClasses = new HashSet<SootClass>(); for(AllocNode n: threadObjs){ RefType type = (RefType)n.getType(); threadClasses.add(type.getSootClass()); } //transfer thread classes to corresponding run() methods FastHierarchy hierarchy = Scene.v().getFastHierarchy(); for(SootClass cls: threadClasses){ SootMethod entry = hierarchy.resolveConcreteDispatch(cls,RUNNABLE_RUN_METHOD); if(entry != THREAD_RUN_METHOD){//ignore Thread.run() threadEntries.add(entry); } } } else if(_ptsTo instanceof DumbPointerAnalysis){ // Transfer thread start on Thread.run() to the actually started thread, // namely the thread entries possibly called inside Thread.run() Iterator<Edge> edges = originalCallGraph.edgesOutOf(unit); for(Targets it = new Targets(edges);it.hasNext();){ SootMethod m = (SootMethod)it.next(); if(m==THREAD_START_METHOD){ } else if(m==THREAD_RUN_METHOD){ Callees callees = new Callees(originalCallGraph,THREAD_RUN_METHOD); threadEntries.addAll(callees.explicits()); } else{ threadEntries.add(m); } } threadEntries.remove(THREAD_RUN_METHOD); } return threadEntries; } private boolean isThreadStartCall(AllocNode node,InvokeExpr expr){ if(expr instanceof SpecialInvokeExpr){ if(expr.getMethod() ==THREAD_START_METHOD) return true; else return false; } else{ FastHierarchy hierarchy = Scene.v().getFastHierarchy(); SootClass cls = ((RefType)node.getType()).getSootClass(); SootMethod start = hierarchy.resolveConcreteDispatch(cls,THREAD_START_METHOD); if(start == THREAD_START_METHOD) return true; else return false; } } /** Collect all SootClass(es) of a given PointsToSet. */ private class ThreadCollector extends P2SetVisitor{ private Collection<AllocNode> _threadObjs; private Set<AllocNode> _checkedObjects; private FastHierarchy _hierarchy; private boolean _checkStartCall; private InvokeExpr _invoke; public ThreadCollector(Collection<AllocNode> threadObjs,boolean checkStartCall,InvokeExpr invoke){ this(threadObjs, new HashSet<AllocNode>(), checkStartCall,invoke); } public ThreadCollector(Collection<AllocNode> threadObjs,Set<AllocNode> checkedObjects, boolean checkStartCall,InvokeExpr invoke){ this._threadObjs = threadObjs; this._hierarchy = Scene.v().getFastHierarchy(); this._checkStartCall = checkStartCall; this._invoke = invoke; this._checkedObjects = checkedObjects; } public void visit(Node n) { if(n instanceof AllocNode){ //To avoid recursive analysis if(_checkedObjects.contains(n)){ return; } AllocNode node = (AllocNode)n; _checkedObjects.add(node); Type t = node.getType(); if(!(t instanceof RefType)){ //XXX: Could be AnySubType return; } RefType type = (RefType)t; SootClass cls = type.getSootClass(); //Check if the start() call is a real call to Thread.start(). //If not, no THREAD edge is added, and here we directly return. if(_checkStartCall){ //FIXME ���ﻹ��Ҫ��һ����Thread+.start()�ıߣ���������ʱû�д��� if(!isThreadStartCall(node,_invoke)) return; } SootMethod run = _hierarchy.resolveConcreteDispatch(cls,RUNNABLE_RUN_METHOD); if(run == THREAD_RUN_METHOD){ //Call Thread.run() on java.lang.Thread or its sub class (subclass may not override run() method) if(cls == THREAD_CLASS && !_isVTA){ //trace to the constructor call of Thread(Runnable r) resolveTgtsFromParam(node,_threadObjs,_checkedObjects); }else{ //searching Thread.target field for the started threads resolveTgtsFromField(node,_threadObjs,_checkedObjects); //throw new RuntimeException("A special use of thread."); } }else{ //Call run() on a sub class of java.lang.Thread overriding Thread.run() _threadObjs.add(node); } } else{ throw new RuntimeException(n.getClass()+" can not occur in a points-to set"); } } } /** Resolve the possible started Runnable(s) from class field Thread.target. * This approach can be very imprecise as this field is assigned in * Thread.init(ThreadGroup,Runnable,...) which is analyzed context-insensitively. * We prefer resolveTgtsFromParams instead. */ private void resolveTgtsFromField(AllocNode node,Collection<AllocNode> threadObjs,Set<AllocNode> checkedObjects){ AllocDotField field = node.dot(THREAD_TARGET_FIELD); PointsToSetInternal targets = field.getP2Set(); P2SetVisitor visitor = new ThreadCollector(threadObjs,checkedObjects,false,null); targets.forall(visitor); } private void resolveTgtsFromParam(AllocNode node,Collection<AllocNode> threadObjs,Set<AllocNode> checkedObjects){ Value allocExpr = (Value)node.getNewExpr(); DefinitionStmt allocStmt = null; Local allocLocal = null; Body body = node.getMethod().getActiveBody(); {//Find the allocation statement and the Local carrying the new allocated object for (Unit unit: body.getUnits()) { if(unit instanceof DefinitionStmt){ DefinitionStmt d = (DefinitionStmt)unit; if(d.getRightOp() == allocExpr){ allocStmt = d; allocLocal = (Local)d.getLeftOp(); break; } } } if (allocStmt == null) throw new RuntimeException("Error to find allocating Unit."); } {//find call to the constructors, set local to be the real thread UnitGraph graph = new BriefUnitGraph(body); BreathFirstSearch<Unit> bsearch = new ConstructorCallSearch(graph,allocStmt,allocLocal,THREAD_CLASS); Collection<Unit> calls = bsearch.search(); InvokeStmt invoke = (InvokeStmt)calls.iterator().next(); List<?> args = invoke.getInvokeExpr().getArgs(); int size = args.size(); Type runableType = RUNNABLE_CLASS.getType(); FastHierarchy hierarchy = Scene.v().getFastHierarchy(); for(int i=0;i<size;i++){ Object ag = args.get(i); if(ag instanceof Local){ Local agLoc = (Local)ag; Type agType = agLoc.getType(); if(hierarchy.canStoreType(agType, runableType)){//agType == runableType){ allocLocal = agLoc; } } } assert(allocLocal!=null);//Else can not find the real thread object. } //if(local!=null){//get the points-to set of Runnable type local PointsToSetInternal tgts =(PointsToSetInternal)_ptsTo.reachingObjects(allocLocal); //If implemented like this: // Runnable r = new RunnableImpl(); // Thread t0 = new Thread(r); // Thread t1 = new Thread(t0); // t1.start(); //Then we must perform recursive analysis as the real threads holden by t0 can still be activated. P2SetVisitor visitor = new ThreadCollector(threadObjs,checkedObjects,false,null); tgts.forall(visitor); //} } private class ConstructorCallSearch extends BreathFirstSearch<Unit>{ public ConstructorCallSearch(UnitGraph cfg,Unit start,Local local,SootClass cls){ super(cfg,start,false); this._local = local; this._cls = cls; } public boolean match(Unit obj){ if(!(obj instanceof InvokeStmt)) return false; InvokeStmt stmt = (InvokeStmt)obj; InvokeExpr expr = stmt.getInvokeExpr(); if(!(expr instanceof InstanceInvokeExpr)) return false; InstanceInvokeExpr invoke = (InstanceInvokeExpr)expr; Local receiver = (Local)invoke.getBase(); if(receiver != _local) return false; SootMethod method = invoke.getMethod(); if(method.getDeclaringClass()!=_cls ||!method.getName().equals("<init>")) return false; return true; } private Local _local; private SootClass _cls; } }