All Downloads are FREE. Search and download functionalities are using the official Maven repository.

soot.jimple.toolkits.infoflow.LocalObjectsAnalysis Maven / Gradle / Ivy

package soot.jimple.toolkits.infoflow;

import soot.*;

import java.util.*;
import soot.toolkits.graph.*;
import soot.toolkits.scalar.*;
import soot.jimple.toolkits.callgraph.*;
import soot.jimple.*;

// LocalObjectsAnalysis written by Richard L. Halpert, 2007-02-24
// Constructs data flow tables for each method of every application class.  Ignores indirect flow.
// These tables conservatively approximate how data flows from parameters,
// fields, and globals to parameters, fields, globals, and the return value.
// Note that a ref-type parameter (or field or global) might allow access to a
// large data structure, but that entire structure will be represented only by
// the parameter's one node in the data flow graph.
// Provides a high level interface to access the data flow information.



public class LocalObjectsAnalysis
{
	public InfoFlowAnalysis dfa;
	UseFinder uf;
	CallGraph cg;

	Map classToClassLocalObjectsAnalysis;
	
	Map mergedContextsCache;
	Map mloaCache;
	
	public LocalObjectsAnalysis(InfoFlowAnalysis dfa)
	{
		this.dfa = dfa;
		this.uf = new UseFinder();
		this.cg = Scene.v().getCallGraph();
		
		classToClassLocalObjectsAnalysis = new HashMap();
		mergedContextsCache = new HashMap();
		mloaCache = new HashMap();
	}
	
	public ClassLocalObjectsAnalysis getClassLocalObjectsAnalysis(SootClass sc)
	{
		if(!classToClassLocalObjectsAnalysis.containsKey(sc))
		{
			ClassLocalObjectsAnalysis cloa = newClassLocalObjectsAnalysis(this, dfa, uf, sc);
			classToClassLocalObjectsAnalysis.put(sc, cloa);
		}
		return classToClassLocalObjectsAnalysis.get(sc);
	}
	
	// meant to be overridden by specialty local objects analyses
	protected ClassLocalObjectsAnalysis newClassLocalObjectsAnalysis(LocalObjectsAnalysis loa, InfoFlowAnalysis dfa, UseFinder uf, SootClass sc)
	{
		return new ClassLocalObjectsAnalysis(loa, dfa, uf, sc);
	}
	
	public boolean isObjectLocalToParent(Value localOrRef, SootMethod sm)
	{
		// Handle obvious case
		if( localOrRef instanceof StaticFieldRef )
			return false;

		ClassLocalObjectsAnalysis cloa = getClassLocalObjectsAnalysis(sm.getDeclaringClass());
		return cloa.isObjectLocal(localOrRef, sm);
	}
	
	public boolean isFieldLocalToParent(SootField sf) // To parent class!
	{
		// Handle obvious case
		if( sf.isStatic() )
			return false;

		ClassLocalObjectsAnalysis cloa = getClassLocalObjectsAnalysis(sf.getDeclaringClass());
		return cloa.isFieldLocal(sf);
	}
	
	public boolean isObjectLocalToContext(Value localOrRef, SootMethod sm, SootMethod context)
	{		
		// Handle special case
		if(sm == context)
		{
//			G.v().out.println("      Directly Reachable: ");
			boolean isLocal = isObjectLocalToParent(localOrRef, sm);
			if(dfa.printDebug())
				G.v().out.println("    " + (isLocal ? 
					"LOCAL  (Directly Reachable from " + context.getDeclaringClass().getShortName() + "." + context.getName() + ")" :
					"SHARED (Directly Reachable from " + context.getDeclaringClass().getShortName() + "." + context.getName() + ")"));
			return isLocal;
		}
	
		// Handle obvious case
		if( localOrRef instanceof StaticFieldRef )
		{
			if(dfa.printDebug())
				G.v().out.println("    SHARED (Static             from " + context.getDeclaringClass().getShortName() + "." + context.getName() + ")");
			return false;
		}

		// Handle uncheckable case
		if(!sm.isConcrete())
		{
			// no way to tell... and how do we have access to a Local anyways???
			throw new RuntimeException("Attempted to check if a local variable in a non-concrete method is shared/local.");
		}

		// For Resulting Merged Context, check if localOrRef is local
		Body b = sm.retrieveActiveBody(); // sm is guaranteed concrete (see above)
		// Check if localOrRef is Local in smContext
/*		SmartMethodLocalObjectsAnalysis mloa = null;
//		Pair mloaKey = new Pair(sm, mergedContext);
		if( mloaCache.containsKey(sm) )
		{
			mloa = (SmartMethodLocalObjectsAnalysis) mloaCache.get(sm);
//			G.v().out.println("      Retrieved mloa From Cache: ");
		}
		else
		{
			UnitGraph g = new ExceptionalUnitGraph(b);
			mloa = new SmartMethodLocalObjectsAnalysis(g, dfa);
//			G.v().out.println("        Caching mloa (smdfa " + SmartMethodInfoFlowAnalysis.counter + 
//				" smloa " + SmartMethodLocalObjectsAnalysis.counter + ") for " + sm.getName() + " on goal:");
			mloaCache.put(sm, mloa);
		}
//*/

		CallLocalityContext mergedContext = getClassLocalObjectsAnalysis(context.getDeclaringClass()).getMergedContext(sm);
		if(mergedContext == null)
		{
			if(dfa.printDebug())
				G.v().out.println("      ------ (Unreachable        from " + context.getDeclaringClass().getShortName() + "." + context.getName() + ")");
			return true; // it's not non-local...
		}

		// with the completed mergedContext...
		// localOrRef can actually be a field ref
		if( localOrRef instanceof InstanceFieldRef )
		{
			InstanceFieldRef ifr = (InstanceFieldRef) localOrRef;
			
			Local thisLocal = null;
			try{ thisLocal = b.getThisLocal(); }
			catch(RuntimeException re) { /* Couldn't get thisLocal */ }
			
			if(ifr.getBase() == thisLocal)
			{
				boolean isLocal = mergedContext.isFieldLocal(InfoFlowAnalysis.getNodeForFieldRef(sm, ifr.getField()));
				if(dfa.printDebug())
				{
					if(isLocal)
					{
						G.v().out.println("      LOCAL  (this  .localField  from " + context.getDeclaringClass().getShortName() + "."
																				   + context.getName() + ")");
					}
					else
					{
						G.v().out.println("      SHARED (this  .sharedField from " + context.getDeclaringClass().getShortName() + "." 
																				   + context.getName() + ")");
					}
				}
				return isLocal;
			}
			else
			{
				boolean isLocal = SmartMethodLocalObjectsAnalysis.isObjectLocal(dfa, sm, mergedContext, ifr.getBase());
				if(isLocal)
				{
					ClassLocalObjectsAnalysis cloa = getClassLocalObjectsAnalysis(context.getDeclaringClass());
					isLocal = !cloa.getInnerSharedFields().contains(ifr.getField());
					if(dfa.printDebug())
					{
						if(isLocal)
						{
							G.v().out.println("      LOCAL  (local .localField  from " + context.getDeclaringClass().getShortName() + "."
																					   + context.getName() + ")");
						}
						else
						{
							G.v().out.println("      SHARED (local .sharedField from " + context.getDeclaringClass().getShortName() + "."
																					   + context.getName() + ")");
						}
					}
					return isLocal;
				}
				else
				{
					if(dfa.printDebug())
						G.v().out.println("      SHARED (shared.someField   from " + context.getDeclaringClass().getShortName() + "."
																			   + context.getName() + ")");
					return isLocal;
				}
			}
		}

		boolean isLocal = SmartMethodLocalObjectsAnalysis.isObjectLocal(dfa, sm, mergedContext, localOrRef);
		if(dfa.printDebug())
		{
			if(isLocal)
			{	
				G.v().out.println("      LOCAL  ( local             from " + context.getDeclaringClass().getShortName() + "."
																		   + context.getName() + ")");
			}
			else
			{
				G.v().out.println("      SHARED (shared             from " + context.getDeclaringClass().getShortName() + "."
																		   + context.getName() + ")");
			}
		}
		return isLocal;
	}

/*	BROKEN	
	public boolean isFieldLocalToContext(SootField sf, SootMethod sm, SootClass context)
	{
		G.v().out.println("    Checking if " + sf + " in " + sm + " is local to " + context + ":");

		if(sm.getDeclaringClass() == context) // special case
		{
			boolean isLocal = isFieldLocalToParent(sf);
			G.v().out.println("      Directly Reachable: " + (isLocal ? "LOCAL" : "SHARED"));
			return isLocal;
		}
	
		// The rest of the time, we must find all call chains from context to sm
		// if it's local on all of them, then return true.
		
		// Find Call Chains (separate chains for separate possible virtual call targets)
		// TODO right now we discard reentrant call chains... but this is UNSAFE
		// TODO right now we are stupid about virtual calls... but this is UNSAFE
		
		// for each method in the context class (OR JUST FROM THE RUN METHOD IF IT'S A THREAD?)
		List classMethods = getAllMethodsForClass(context); // gets methods in context class and superclasses
		List callChains = new ArrayList();
		List startingMethods = new ArrayList();
		Iterator classMethodsIt = classMethods.iterator();
		while(classMethodsIt.hasNext())
		{
			SootMethod classMethod = (SootMethod) classMethodsIt.next();
			List methodCallChains = getCallChainsBetween(classMethod, sm);
			Iterator methodCallChainsIt = methodCallChains.iterator();
			while(methodCallChainsIt.hasNext())
			{
				callChains.add(methodCallChainsIt.next());
				startingMethods.add(classMethod); // need to add this once for each method call chain being added
			}
		}
		
		if(callChains.size() == 0)
		{
			G.v().out.println("      Unreachable: treat as local.");
			return true; // it's not non-local...
		}
		G.v().out.println("      Found " + callChains.size() + " Call Chains...");
//		for(int i = 0; i < callChains.size(); i++)
//			G.v().out.println("      " + callChains.get(i));
		
		// Check Call Chains
		for(int i = 0; i < callChains.size(); i++)
		{
			List callChain = (List) callChains.get(i);
			if(!isFieldLocalToContextViaCallChain(sf, sm, context, (SootMethod) startingMethods.get(i), callChain))
			{
				G.v().out.println("      SHARED");
				return false;
			}
		}
		G.v().out.println("      LOCAL");
		return true;
	}
*/
	Map rmCache = new HashMap();
	
	public CallChain getNextCallChainBetween(SootMethod start, SootMethod goal, List previouslyFound)
	{
//		callChains.add(new LinkedList()); // Represents the one way to get from goal to goal (which is to already be there)

		// Is this worthwhile?  Fast?  Slow?  Broken?  Applicable inside the recursive method?
		// If method is unreachable, don't bother trying to make chains
		// CACHEABLE?
		ReachableMethods rm = null;
		if(rmCache.containsKey(start))
			rm = rmCache.get(start);
		else
		{
			List entryPoints = new ArrayList();
			entryPoints.add(start);
			rm = new ReachableMethods(cg, entryPoints);
			rm.update();
			rmCache.put(start, rm);
		}
		
		if(rm.contains(goal))
		{
//			Set methodsInAnyChain = new HashSet();
//			methodsInAnyChain.add(goal);
			return getNextCallChainBetween(rm, start, goal, null, null, previouslyFound);
		}
		
		return null; // new ArrayList();
	}

	Map callChainsCache = new HashMap();

	public CallChain getNextCallChainBetween(ReachableMethods rm, SootMethod start, SootMethod end, Edge endToPath, CallChain path, List previouslyFound)
	{
		Pair cacheKey = new Pair(start, end);
		if(callChainsCache.containsKey(cacheKey))
		{
//        	G.v().out.print("C");
			return null;
//			return (CallChain) callChainsCache.get(cacheKey);
		}
		path = new CallChain(endToPath, path); // initially, path and endToPath can be null
		if(start == end)
		{
//			if(previouslyFound.contains(path)) // don't return a call chain that was already returned in a previous run
//			{
//				G.v().out.print("P");
//				return null;
//			}

//			G.v().out.print("F");
			return path;

//			List ret = new ArrayList();
//			ret.add(path);
//			G.v().out.print("F");
//			return ret;
        }

        if(!rm.contains(end))
        {
//        	G.v().out.print("U");
        	return null; // new ArrayList(); // no paths
        }

//		List paths = new ArrayList(); // no paths

		Iterator edgeIt = cg.edgesInto(end);
		while(edgeIt.hasNext())
		{
			Edge e = (Edge) edgeIt.next();
			SootMethod node = e.src();
			if(!path.containsMethod(node) && e.isExplicit() && e.srcStmt().containsInvokeExpr())
			{
//	        	G.v().out.print("R");
				CallChain newpath = getNextCallChainBetween(rm, start, node, e, path, previouslyFound); // node is supposed to be a method
				if(newpath != null)
				{
//		        	G.v().out.print("|");
					if(!previouslyFound.contains(newpath))
						return newpath;
				}
//				Iterator newpathsIt = newpaths.iterator();
//				while(newpathsIt.hasNext())
//				{
//					paths.addAll(newpaths);
//				}
			}
			else
			{
//	        	G.v().out.print("S");
			}
		}
//		G.v().out.print("(" + paths.size() + ")");
//		if(paths.size() < 100)
		if(previouslyFound.size() == 0)
			callChainsCache.put(cacheKey, null);
//		G.v().out.print("|");
		return null;
	}

/*	
	// callChains go from current to goal
	public void getCallChainsBetween(SootMethod start, SootMethod current, SootMethod goal, ReachableMethods rm, List callChains, Set methodsInAnyChain)
	{
		List oldCallChains = new ArrayList();
		oldCallChains.addAll(callChains);
		callChains.clear();

		Pair cacheKey = new Pair(start, current);
		if(callChainsCache.containsKey(cacheKey))
		{
			List cachedChains = (List) callChainsCache.get(cacheKey);

			Iterator cachedChainsIt = cachedChains.iterator();
			while(cachedChainsIt.hasNext())
			{
				CallChain cachedChain = (CallChain) cachedChainsIt.next();
				Iterator oldCallChainsIt = oldCallChains.iterator();
				while(oldCallChainsIt.hasNext())
				{
					CallChain oldChain = (CallChain) oldCallChainsIt.next();
					callChains.add(cachedChain.cloneAndExtend(oldChain));
				}
			}
			
			// We now have chains from start to goal
			
			G.v().out.print("C");
			return;
		}

		// For each edge into goal, clone the existing call chains and add that edge to the beginning, then call self with new goal
		Iterator edgeIt = cg.edgesInto(current);
		while(edgeIt.hasNext())
		{
			Edge e = (Edge) edgeIt.next();
//			Stmt currentCallerStmt = e.srcStmt();
			SootMethod currentCaller = e.src();
			
			// If the source of this edge is unreachable, ignore it
			if( !rm.contains(currentCaller) )
			{
				G.v().out.print("U");
				continue;
			}

			// If this would introduce an SCC, skip it (TODO: Deal with it, instead)
			boolean currentCallerIsAlreadyInAChain = false;
			Iterator oldCallChainsIt = oldCallChains.iterator();
			while(oldCallChainsIt.hasNext())
			{
				CallChain oldCallChain = (CallChain) oldCallChainsIt.next();
				if(oldCallChain.containsMethod(currentCaller))
				{
					currentCallerIsAlreadyInAChain = true;
					break;
				}
			}
				
			if( ( currentCaller == goal ) || currentCallerIsAlreadyInAChain) // methodsInAnyChain.contains(goalCaller) )
			{
				G.v().out.print("S");
				continue; // if this goalCaller would be an SCC, ignore it
			}

			// If this is the type of edge that we'd like to include in our call chains
			if(e.isExplicit())// && goalCallerStmt.containsInvokeExpr())
			{
				// Make a copy of all call chains
//				List newCallChains = cloneCallChains(oldCallChains);
				List newCallChains = new ArrayList();
				
				if(oldCallChains.size() == 0)
				{
					newCallChains.add(new CallChain(e, null));
				}
				else
				{
					// Add this edge to each call chain
					oldCallChainsIt = oldCallChains.iterator();
					while(oldCallChainsIt.hasNext())
					{
						CallChain oldCallChain = (CallChain) oldCallChainsIt.next();
						newCallChains.add(new CallChain(e, oldCallChain));
					}
				}
				
//				methodsInAnyChain.add(goalCaller);
					
				// If the call chains don't now start from start, then get ones that do (recursively)
				if(currentCaller != start)
				{
					G.v().out.print("R");

					// Call self to extend these new call chains all the way to start
					getCallChainsBetween(start, currentCaller, goal, rm, newCallChains, methodsInAnyChain);
				}
				else
				{
					G.v().out.print("F");
				}
				
				// Add all the new call chains to our set
				callChains.addAll(newCallChains);
			}
		}
		G.v().out.print("(" + callChains.size() + ")");
		if(callChains.size() > 0)
			callChainsCache.put(new Pair(start, goal), callChains);
	}
*/
/*
	// returns a 1-deep clone of a List of Lists
	private List cloneCallChains(List callChains)
	{
		List ret = new ArrayList();
		Iterator callChainsIt = callChains.iterator();
		while(callChainsIt.hasNext())
			ret.add( ((LinkedList) callChainsIt.next()).clone() ); // add a clone of each call chain
		return ret;
	}
*/	
/*
	public List getCallChainsBetween(SootMethod start, SootMethod goal)
	{
		G.v().out.print("Q");
		List callChains = new ArrayList();
		Iterator edgeIt = cg.edgesInto(goal);
		while(edgeIt.hasNext())
		{
			Edge e = (Edge) edgeIt.next();
			Stmt goalCallerStmt = e.srcStmt();
			SootMethod goalCaller = e.src();
			if(e.isExplicit() && goalCallerStmt.containsInvokeExpr()) // if not, we're not interested
			{
				List edgeCallChains = null;
				if(goalCaller == start)
				{
					edgeCallChains = new ArrayList();
					edgeCallChains.add(new LinkedList());
				}
				else
				{
					edgeCallChains = getCallChainsBetween(start, goalCaller);
				}

				Pair pair = new Pair(new EquivalentValue(goalCallerStmt.getInvokeExpr()), goal);
				Iterator edgeCallChainsIt = edgeCallChains.iterator();
				while(edgeCallChainsIt.hasNext())
				{
					List edgeCallChain = (List) edgeCallChainsIt.next();
					if( !edgeCallChain.contains(pair) ) // SCC, we must ignore, sadly... TODO FIX THIS
					{
						edgeCallChain.add(pair);
						callChains.add(edgeCallChain);
					}
				}
			}
		}
		return callChains;
	}
*/

	// returns a list of all methods that can be invoked on an object of type sc
	public List getAllMethodsForClass(SootClass sootClass)
	{
		// Determine which methods are reachable in this program
		ReachableMethods rm = Scene.v().getReachableMethods();

		// Get list of reachable methods declared in this class
		// Also get list of fields declared in this class
		List scopeMethods = new ArrayList();
		Iterator scopeMethodsIt = sootClass.methodIterator();
		while(scopeMethodsIt.hasNext())
		{
			SootMethod scopeMethod = (SootMethod) scopeMethodsIt.next();
			if(rm.contains(scopeMethod))
				scopeMethods.add(scopeMethod);
		}
		
		// Add reachable methods and fields declared in superclasses
		SootClass superclass = sootClass;
		if(superclass.hasSuperclass())
			superclass = sootClass.getSuperclass();
		while(superclass.hasSuperclass()) // we don't want to process Object
		{
	        Iterator scMethodsIt = superclass.methodIterator();
	        while(scMethodsIt.hasNext())
	        {
				SootMethod scMethod = (SootMethod) scMethodsIt.next();
				if(rm.contains(scMethod))
					scopeMethods.add(scMethod);
	        }
			superclass = superclass.getSuperclass();
		}
		return scopeMethods;
	}
	
	public boolean hasNonLocalEffects(SootMethod containingMethod, InvokeExpr ie, SootMethod context)
	{
		SootMethod target = ie.getMethodRef().resolve();
		MutableDirectedGraph dataFlowGraph = dfa.getMethodInfoFlowSummary(target); // TODO actually we want a graph that is sensitive to scalar data, too
		
		// For a static invoke, check if any fields or any shared params are read/written
		if(ie instanceof StaticInvokeExpr)
		{
			Iterator graphIt = dataFlowGraph.iterator();
			while(graphIt.hasNext())
			{
				EquivalentValue nodeEqVal = (EquivalentValue) graphIt.next();
				Ref node = (Ref) nodeEqVal.getValue();
				if(node instanceof FieldRef)
				{
					if( dataFlowGraph.getPredsOf(nodeEqVal).size() > 0 ||
						dataFlowGraph.getSuccsOf(nodeEqVal).size() > 0 )
					{
						return true;
					}
				}
				else if(node instanceof ParameterRef)
				{
					if( dataFlowGraph.getPredsOf(nodeEqVal).size() > 0 ||
						dataFlowGraph.getSuccsOf(nodeEqVal).size() > 0 )
					{
						ParameterRef pr = (ParameterRef) node;
						if(pr.getIndex() != -1)
						{
							if( !isObjectLocalToContext(ie.getArg(pr.getIndex()), containingMethod, context) )
								return true;
						}
					}
				}
			}
		}
		else if(ie instanceof InstanceInvokeExpr)
		{
			// For a instance invoke on local object, check if any static fields or any shared params are read/written
			InstanceInvokeExpr iie = (InstanceInvokeExpr) ie;
			if( isObjectLocalToContext(iie.getBase(), containingMethod, context) )
			{
				Iterator graphIt = dataFlowGraph.iterator();
				while(graphIt.hasNext())
				{
					EquivalentValue nodeEqVal = (EquivalentValue) graphIt.next();
					Ref node = (Ref) nodeEqVal.getValue();
					if(node instanceof StaticFieldRef)
					{
						if( dataFlowGraph.getPredsOf(nodeEqVal).size() > 0 ||
							dataFlowGraph.getSuccsOf(nodeEqVal).size() > 0 )
						{
							return true;
						}
					}
					else if(node instanceof ParameterRef)
					{
						if( dataFlowGraph.getPredsOf(nodeEqVal).size() > 0 ||
							dataFlowGraph.getSuccsOf(nodeEqVal).size() > 0 )
						{
							ParameterRef pr = (ParameterRef) node;
							if(pr.getIndex() != -1)
							{
								if( !isObjectLocalToContext(ie.getArg(pr.getIndex()), containingMethod, context) )
									return true;
							}
						}
					}
				}
			}
			// For a instance invoke on shared object, check if any fields or any shared params are read/written
			else
			{
				Iterator graphIt = dataFlowGraph.iterator();
				while(graphIt.hasNext())
				{
					EquivalentValue nodeEqVal = (EquivalentValue) graphIt.next();
					Ref node = (Ref) nodeEqVal.getValue();
					if(node instanceof FieldRef)
					{
						if( dataFlowGraph.getPredsOf(nodeEqVal).size() > 0 ||
							dataFlowGraph.getSuccsOf(nodeEqVal).size() > 0 )
						{
							return true;
						}
					}
					else if(node instanceof ParameterRef)
					{
						if( dataFlowGraph.getPredsOf(nodeEqVal).size() > 0 ||
							dataFlowGraph.getSuccsOf(nodeEqVal).size() > 0 )
						{
							ParameterRef pr = (ParameterRef) node;
							if(pr.getIndex() != -1)
							{
								if( !isObjectLocalToContext(ie.getArg(pr.getIndex()), containingMethod, context) )
									return true;
							}
						}
					}
				}
			}
		}
		return false;
	}
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy