/** * Copyright (C) 2013 Rohan Padhye * * 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 program. If not, see <http://www.gnu.org/licenses/>. * */ package vasco.callgraph; import java.util.Collections; import java.util.HashSet; import java.util.Set; import soot.Local; import soot.RefLikeType; import soot.RefType; import soot.Scene; import soot.SootClass; import soot.SootField; import soot.SootMethod; import soot.Unit; import soot.Value; import soot.jimple.AnyNewExpr; import soot.jimple.ArrayRef; import soot.jimple.AssignStmt; import soot.jimple.CastExpr; import soot.jimple.CaughtExceptionRef; import soot.jimple.Constant; import soot.jimple.DefinitionStmt; import soot.jimple.IdentityRef; import soot.jimple.InstanceFieldRef; import soot.jimple.InstanceInvokeExpr; import soot.jimple.IntConstant; import soot.jimple.InterfaceInvokeExpr; import soot.jimple.InvokeExpr; import soot.jimple.InvokeStmt; import soot.jimple.NewArrayExpr; import soot.jimple.ReturnStmt; import soot.jimple.SpecialInvokeExpr; import soot.jimple.StaticFieldRef; import soot.jimple.StaticInvokeExpr; import soot.jimple.Stmt; import soot.jimple.VirtualInvokeExpr; import soot.jimple.internal.JNewArrayExpr; import vasco.CallSite; import vasco.Context; import vasco.OldForwardInterProceduralAnalysis; import vasco.ProgramRepresentation; import vasco.soot.DefaultJimpleRepresentation; /** * An inter-procedural analysis for constructing a context-sensitive call graph * on-the-fly. * * <p>This analysis uses the value-based context-sensitive inter-procedural framework * and was developed as an example instantiation of the framework.</p> * * <p>The analysis uses {@link PointsToGraph} objects as data flow values, which * in turn abstracts heap locations using allocation sites.<p> * * <p><strong>Warning!</strong> The current implementation of this class uses * the old API (see {@link OldForwardInterProceduralAnalysis}) without separate * call/return flow functions. The developer is currently in the process of migrating * this implementation to the new API (see {@link vasco.ForwardInterProceduralAnalysis ForwardInterProceduralAnalysis}).</p> * * @author Rohan Padhye */ public class PointsToAnalysis extends OldForwardInterProceduralAnalysis<SootMethod,Unit,PointsToGraph> { private static final SootMethod DUMMY_METHOD = new SootMethod("DUMMY_METHOD", Collections.EMPTY_LIST, Scene.v().getObjectType()); /** * A shared points-to graph that maintains information about objects * reachable from static fields (modelled as fields of a dummy global variable). * * <p>For static load/store statements, we union this points-to graph with the * points-to graph in the flow function, perform the operation, and then * separate stuff out again.</p> */ private PointsToGraph staticHeap; /** * A set of classes whose static initialisers have been processed. */ private Set<SootClass> clinitCalled; /** * Constructs a new points-to analysis as a forward-flow inter-procedural * analysis. */ public PointsToAnalysis() { super(); // Play around with these flags this.freeResultsOnTheFly = true; this.verbose = true; // No classes statically initialised yet this.clinitCalled = new HashSet<SootClass>(); // Create a static points-to graph with a single "global" root object this.staticHeap = topValue(); this.staticHeap.assignNew(PointsToGraph.GLOBAL_LOCAL, PointsToGraph.GLOBAL_SITE); } /** * Returns a points-to graph with the locals of main initialised to * <tt>null</tt>, except the command-line arguments which are * initialised to an array of strings. */ @Override public PointsToGraph boundaryValue(SootMethod entryPoint) { // For now we only support entry to the main method assert(entryPoint == Scene.v().getMainMethod()); // Ok, start setting up entry value PointsToGraph entryValue = new PointsToGraph(); // Locals of main... (only reference types) SootMethod mainMethod = Scene.v().getMainMethod(); for (Local local : mainMethod.getActiveBody().getLocals()) { if (local.getType() instanceof RefLikeType) { entryValue.assign(local, null); } } // Command-line arguments to main... Local argsLocal = mainMethod.getActiveBody().getParameterLocal(0); NewArrayExpr argsExpr = new JNewArrayExpr(Scene.v().getRefType("java.lang.String"), IntConstant.v(0)); entryValue.assignNew(argsLocal, argsExpr); entryValue.setFieldConstant(argsLocal, PointsToGraph.ARRAY_FIELD, PointsToGraph.STRING_CONST); return entryValue; } /** * Returns a copy of the given points-to graph. */ @Override public PointsToGraph copy(PointsToGraph graph) { return new PointsToGraph(graph); } /** * Performs operations on points-to graphs depending on the statement inside * a CFG node. */ @Override protected PointsToGraph flowFunction(Context<SootMethod,Unit,PointsToGraph> context, Unit unit, PointsToGraph in) { // First set OUT to copy of IN (this is default for most statements). PointsToGraph out = new PointsToGraph(in); // This analysis is written assuming that units are statements (and not, // for example, basic blocks) assert (unit instanceof Stmt); Stmt stmt = (Stmt) unit; // What kind of statement? if (stmt instanceof DefinitionStmt) { // Assignment of LHS to an RHS Value lhsOp = ((DefinitionStmt) stmt).getLeftOp(); Value rhsOp = ((DefinitionStmt) stmt).getRightOp(); // Invoke static initialisers if static members accessed // for the first time StaticFieldRef staticReference = null; if (lhsOp instanceof StaticFieldRef) { staticReference = ((StaticFieldRef) lhsOp); } else if (rhsOp instanceof StaticFieldRef) { staticReference = ((StaticFieldRef) rhsOp); } if (staticReference != null) { SootClass declaringClass = staticReference.getField().getDeclaringClass(); if (clinitCalled.contains(declaringClass) == false) { clinitCalled.add(declaringClass); // Don't initialise library classes if (declaringClass.isLibraryClass()) { // Set all static fields to null for (SootField field : declaringClass.getFields()) { // Only for static reference fields if (field.isStatic() && field.getType() instanceof RefLikeType) { staticHeap.setFieldSummary(PointsToGraph.GLOBAL_LOCAL, field); } } } else { // We have to initialise this class... if (declaringClass.declaresMethodByName("<clinit>")) { // Get the static initialisation method SootMethod clinit = declaringClass.getMethodByName("<clinit>"); // At its entry use a blank value (with STICKY to avoid TOP termination) PointsToGraph clinitEntryValue = topValue(); clinitEntryValue.assign(PointsToGraph.STICKY_LOCAL, null); // Make the call! this.processCall(context, stmt, clinit,clinitEntryValue); // Do not process this statement now, wait for clinit to return // and this statement as a "return site" return null; } // If no <clinit> defined for this class, then continue as normal :-) } } } // Handle statement depending on type if (lhsOp.getType() instanceof RefLikeType) { // Both LHS and RHS are RefLikeType if (lhsOp instanceof InstanceFieldRef || lhsOp instanceof ArrayRef) { // SETFIELD Local lhs = (Local)(lhsOp instanceof InstanceFieldRef ? ((InstanceFieldRef) lhsOp).getBase() : ((ArrayRef) lhsOp).getBase()); SootField field = lhsOp instanceof InstanceFieldRef ? ((InstanceFieldRef) lhsOp).getField() : PointsToGraph.ARRAY_FIELD; // RHS can be a local or constant (string, class, null) if (rhsOp instanceof Local) { Local rhs = (Local) rhsOp; out.setField(lhs, field, rhs); } else if (rhsOp instanceof Constant) { Constant rhs = (Constant) rhsOp; out.setFieldConstant(lhs, field, rhs); } else { throw new RuntimeException(rhsOp.toString()); } } else if (rhsOp instanceof InstanceFieldRef || rhsOp instanceof ArrayRef) { // GETFIELD Local rhs = (Local)(rhsOp instanceof InstanceFieldRef ? ((InstanceFieldRef) rhsOp).getBase() : ((ArrayRef) rhsOp).getBase()); SootField field = rhsOp instanceof InstanceFieldRef ? ((InstanceFieldRef) rhsOp).getField() : PointsToGraph.ARRAY_FIELD; // LHS has to be local if (lhsOp instanceof Local) { Local lhs = (Local) lhsOp; out.getField(lhs, rhs, field); } else { throw new RuntimeException(lhsOp.toString()); } } else if (rhsOp instanceof AnyNewExpr) { // NEW, NEWARRAY or NEWMULTIARRAY AnyNewExpr anyNewExpr = (AnyNewExpr) rhsOp; if (lhsOp instanceof Local) { Local lhs = (Local) lhsOp; out.assignNew(lhs, anyNewExpr); } else { throw new RuntimeException(lhsOp.toString()); } } else if (rhsOp instanceof InvokeExpr) { // STATICINVOKE, SPECIALINVOKE, VIRTUALINVOKE or INTERFACEINVOKE InvokeExpr expr = (InvokeExpr) rhsOp; // Handle method invocation! out = handleInvoke(context, stmt, expr, in); } else if (lhsOp instanceof StaticFieldRef) { // Get parameters SootField staticField = ((StaticFieldRef) lhsOp).getField(); // Temporarily union locals and globals PointsToGraph tmp = topValue(); tmp.union(out, staticHeap); // Store RHS into static field if (rhsOp instanceof Local) { Local rhsLocal = (Local) rhsOp; tmp.setField(PointsToGraph.GLOBAL_LOCAL, staticField, rhsLocal); } else if (rhsOp instanceof Constant) { Constant rhsConstant = (Constant) rhsOp; tmp.setFieldConstant(PointsToGraph.GLOBAL_LOCAL, staticField, rhsConstant); } else { throw new RuntimeException(rhsOp.toString()); } // Now get rid of all locals, params, etc. Set<Local> locals = new HashSet<Local>(tmp.roots.keySet()); for (Local local : locals) { // Everything except the GLOBAL must go! if (local != PointsToGraph.GLOBAL_LOCAL) { tmp.kill(local); } } // Global information is updated! staticHeap = tmp; } else if (rhsOp instanceof StaticFieldRef) { // Get parameters Local lhsLocal = (Local) lhsOp; SootField staticField = ((StaticFieldRef) rhsOp).getField(); // Temporarily union locals and globals PointsToGraph tmp = topValue(); tmp.union(out, staticHeap); // Load static field into LHS local tmp.getField(lhsLocal, PointsToGraph.GLOBAL_LOCAL, staticField); // Now get rid of globals that we do not care about tmp.kill(PointsToGraph.GLOBAL_LOCAL); // Local information is updated! out = tmp; } else if (rhsOp instanceof CaughtExceptionRef) { Local lhs = (Local) lhsOp; out.assignSummary(lhs); } else if (rhsOp instanceof IdentityRef) { // Ignore identities } else if (lhsOp instanceof Local) { // Assignment Local lhs = (Local) lhsOp; // RHS op is a local, constant or class cast if (rhsOp instanceof Local) { Local rhs = (Local) rhsOp; out.assign(lhs, rhs); } else if (rhsOp instanceof Constant) { Constant rhs = (Constant) rhsOp; out.assignConstant(lhs, rhs); } else if (rhsOp instanceof CastExpr) { Value op = ((CastExpr) rhsOp).getOp(); if (op instanceof Local) { Local rhs = (Local) op; out.assign(lhs, rhs); } else if (op instanceof Constant) { Constant rhs = (Constant) op; out.assignConstant(lhs, rhs); } else { throw new RuntimeException(op.toString()); } } else { throw new RuntimeException(rhsOp.toString()); } } else { throw new RuntimeException(unit.toString()); } } else if (rhsOp instanceof InvokeExpr) { // For non-reference types, only method invocations are important InvokeExpr expr = (InvokeExpr) rhsOp; // Handle method invocation! out = handleInvoke(context, stmt, expr, in); } } else if (stmt instanceof InvokeStmt) { // INVOKE without a return InvokeExpr expr = stmt.getInvokeExpr(); // Handle method invocation! out = handleInvoke(context, stmt, expr, in); } else if (stmt instanceof ReturnStmt) { // Returning a value (not return-void as those are of type ReturnVoidStmt) Value op = ((ReturnStmt) stmt).getOp(); Local lhs = PointsToGraph.RETURN_LOCAL; // We only care about reference-type returns if (op.getType() instanceof RefLikeType) { // We can return a local or a constant if (op instanceof Local) { Local rhs = (Local) op; out.assign(lhs, rhs); } else if (op instanceof Constant) { Constant rhs = (Constant) op; out.assignConstant(lhs, rhs); } else { throw new RuntimeException(op.toString()); } } } return out; } /** * Computes the targets of an invoke expression using a given points-to graph. * * <p>For static invocations, there is only target. For instance method * invocations, the targets depend on the type of receiver objects pointed-to * by the instance variable whose method is being invoked.</p> * * <p>If the instance variable points to a summary node, then the returned * value is <tt>null</tt> signifying a <em>default</em> call-site.</p> */ private Set<SootMethod> getTargets(SootMethod callerMethod, Stmt callStmt, InvokeExpr ie, PointsToGraph ptg) { Set<SootMethod> targets = new HashSet<SootMethod>(); SootMethod invokedMethod = ie.getMethod(); String subsignature = invokedMethod.getSubSignature(); // Static and special invocations refer to the target method directly if (ie instanceof StaticInvokeExpr || ie instanceof SpecialInvokeExpr) { targets.add(invokedMethod); return targets; } else { assert (ie instanceof InterfaceInvokeExpr || ie instanceof VirtualInvokeExpr); // Get the receiver Local receiver = (Local) ((InstanceInvokeExpr) ie).getBase(); // Get what objects the receiver points-to Set<AnyNewExpr> heapNodes = ptg.getTargets(receiver); if (heapNodes != null) { // For each object, find the invoked method for the declared type for (AnyNewExpr heapNode : heapNodes) { if (heapNode == PointsToGraph.SUMMARY_NODE) { // If even one pointee is a summary node, then this is a default site return null; } else if (heapNode instanceof NewArrayExpr) { // Probably getClass() or something like that on an array return null; } // Find the top-most class that declares a method with the given // signature and add it to the resulting targets SootClass sootClass = ((RefType) heapNode.getType()).getSootClass(); do { if (sootClass.declaresMethod(subsignature)) { targets.add(sootClass.getMethod(subsignature)); break; } else if (sootClass.hasSuperclass()) { sootClass = sootClass.getSuperclass(); } else { sootClass = null; } } while (sootClass != null); } } if (targets.isEmpty()) { // System.err.println("Warning! Null call at: " + callStmt+ " in " + callerMethod); } return targets; } } private Set<SootMethod> getDummyTarget() { Set<SootMethod> targets = new HashSet<SootMethod>(); targets.add(DUMMY_METHOD); return targets; } /** * Handles a call site by resolving the targets of the method invocation. * * The resultant flow is the union of the exit flows of all the analysed * callees. If the method returns a reference-like value, this is also taken * into account. */ protected PointsToGraph handleInvoke(Context<SootMethod,Unit,PointsToGraph> callerContext, Stmt callStmt, InvokeExpr ie, PointsToGraph in) { // Get the caller method SootMethod callerMethod = callerContext.getMethod(); // Initialise the final result as TOP first PointsToGraph resultFlow = topValue(); // If this statement is an assignment to an object, then set LHS for // RETURN values. Local lhs = null; Value lhsOp = null; if (callStmt instanceof AssignStmt) { lhsOp = ((AssignStmt) callStmt).getLeftOp(); if (lhsOp.getType() instanceof RefLikeType) { lhs = (Local) lhsOp; } } // Find target methods for this call site (invoke expression) using the points-to data Set<SootMethod> targets = getTargets(callerMethod, callStmt, ie, in); // If "targets" is null, that means the invoking instance was SUMMARY // So we use the DUMMY METHOD (which is a method with no body) if (targets == null) { targets = getDummyTarget(); this.contextTransitions.addTransition(new CallSite<SootMethod,Unit,PointsToGraph>(callerContext, callStmt), null); if (verbose) { System.out.println("[DEF] X" + callerContext + " -> DEFAULT " + ie.getMethod()); } } // Make calls for all target methods for (SootMethod calledMethod : targets) { // The call-edge is obtained by assign parameters and THIS, and killing caller's locals PointsToGraph callEdge = copy(in); if (calledMethod.hasActiveBody()) { // We need to maintain a set of locals not to kill (in case the call is recursive) Set<Local> doNotKill = new HashSet<Local>(); // Initialise sticky parameter callEdge.assign(PointsToGraph.STICKY_LOCAL, null); doNotKill.add(PointsToGraph.STICKY_LOCAL); // Assign this... if (ie instanceof InstanceInvokeExpr) { Local receiver = (Local)((InstanceInvokeExpr) ie).getBase(); Local thisLocal = calledMethod.getActiveBody().getThisLocal(); callEdge.assign(thisLocal, receiver); doNotKill.add(thisLocal); // Sticky it! callEdge.assignSticky(PointsToGraph.STICKY_LOCAL, thisLocal); } // Assign parameters... for (int i = 0; i < calledMethod.getParameterCount(); i++) { // Only for reference-like parameters if (calledMethod.getParameterType(i) instanceof RefLikeType) { Local parameter = calledMethod.getActiveBody().getParameterLocal(i); Value argValue = ie.getArg(i); // The argument can be a constant or local, so handle accordingly if (argValue instanceof Local) { Local argLocal = (Local) argValue; callEdge.assign(parameter, argLocal); doNotKill.add(parameter); // Sticky it! callEdge.assignSticky(PointsToGraph.STICKY_LOCAL, argLocal); } else if (argValue instanceof Constant) { Constant argConstant = (Constant) argValue; callEdge.assignConstant(parameter, argConstant); doNotKill.add(parameter); // No need to sticky constants as caller does not store them anyway } else { throw new RuntimeException(argValue.toString()); } } } // Kill caller data... for (Local callerLocal : callerMethod.getActiveBody().getLocals()) { if (doNotKill.contains(callerLocal) == false) callEdge.kill(callerLocal); } // There should be no "return local", but we kill it anyway (just in case) callEdge.kill(PointsToGraph.RETURN_LOCAL); // Create callee locals.. for (Local calleeLocal : calledMethod.getActiveBody().getLocals()) { if (calleeLocal.getType() instanceof RefLikeType && doNotKill.contains(calleeLocal) == false) { callEdge.assign(calleeLocal, null); } } } // The intra-procedural edge is the IN value minus the objects from the call edge PointsToGraph intraEdge = copy(in); if (lhs != null) { // Oh, and remove the LHS targets too intraEdge.assign(lhs, null); } //intraEdge.subtractHeap(callEdge); // Value at the start of the called procedure is // whatever went through the call edge PointsToGraph entryFlow = callEdge; // Make the call to this method!! (in case of body-less methods, no change) PointsToGraph exitFlow = calledMethod.hasActiveBody() ? processCall(callerContext, callStmt, calledMethod, entryFlow) : entryFlow; // If the called context was analysed, exitFlow will be set, else it // will be null. if (exitFlow != null) { // Propagate stuff from called procedure's exit to the caller's return-site PointsToGraph returnEdge = copy(exitFlow); // Two ways to handle this: if (calledMethod.hasActiveBody()) { // Kill all the called method's locals. That's right. for (Local calleeLocal : calledMethod.getActiveBody().getLocals()) { returnEdge.kill(calleeLocal); } // Remove the stickies (so not to interfere with stickies in the intra-edge) // but do not collect unreachable nodes returnEdge.killWithoutGC(PointsToGraph.STICKY_LOCAL); } // Let's unite the intra-edge with the return edge PointsToGraph callOut = topValue(); callOut.union(intraEdge, returnEdge); // Now we are only left with the return value, if any if (calledMethod.hasActiveBody()) { if (lhs != null) { callOut.assign(lhs, PointsToGraph.RETURN_LOCAL); } // Kill the @return variable whether there was an LHS or not callOut.kill(PointsToGraph.RETURN_LOCAL); } else { // Handle returned objects for native methods if (lhs != null) { // If a string is returned, optimise if (lhs.getType().equals(PointsToGraph.STRING_CONST.getType())) { callOut.assignConstant(lhs, PointsToGraph.STRING_CONST); } else if (lhs.getType().equals(PointsToGraph.CLASS_CONST.getType())) { callOut.assignConstant(lhs, PointsToGraph.CLASS_CONST); } else { // Have to assume the worst! //System.err.println("Warning! Summary node returned at " + // callStmt + " in " + callerMethod); callOut.assignSummary(lhs); } } // Also assume that all parameters are modified for (int i = 0; i < calledMethod.getParameterCount(); i++) { // Only for reference-like parameters if (calledMethod.getParameterType(i) instanceof RefLikeType) { Value argValue = ie.getArg(i); // Summarize if the argument is local (i.e. not a constant) if (argValue instanceof Local) { Local argLocal = (Local) argValue; callOut.summarizeTargetFields(argLocal); } } } } // As we may have multiple virtual calls, merge the value at OUT // of this target's call-site with an accumulator (resultFlow) resultFlow = meet(resultFlow, callOut); } } // If at least one call succeeded, result flow is not TOP if (resultFlow.equals(topValue())) { return null; } else { return resultFlow; } } /** * Returns the union of two points-to graphs. */ @Override public PointsToGraph meet(PointsToGraph op1, PointsToGraph op2) { PointsToGraph result = new PointsToGraph(); result.union(op1, op2); return result; } /** * The default data flow value (lattice top) is the empty points-to graph. */ @Override public PointsToGraph topValue() { return new PointsToGraph(); } @Override public ProgramRepresentation<SootMethod, Unit> programRepresentation() { return DefaultJimpleRepresentation.v(); } }