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

soot.jimple.infoflow.solver.cfg.InfoflowCFG Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) 2012 Secure Software Engineering Group at EC SPRIDE.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Contributors: Christian Fritz, Steven Arzt, Siegfried Rasthofer, Eric
 * Bodden, and others.
 ******************************************************************************/
package soot.jimple.infoflow.solver.cfg;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import heros.solver.IDESolver;
import soot.*;
import soot.jimple.AssignStmt;
import soot.jimple.FieldRef;
import soot.jimple.InvokeExpr;
import soot.jimple.StaticFieldRef;
import soot.jimple.Stmt;
import soot.jimple.VirtualInvokeExpr;
import soot.jimple.toolkits.callgraph.Edge;
import soot.jimple.toolkits.ide.icfg.BiDiInterproceduralCFG;
import soot.jimple.toolkits.ide.icfg.JimpleBasedInterproceduralCFG;
import soot.toolkits.exceptions.ThrowableSet;
import soot.toolkits.graph.DirectedGraph;
import soot.toolkits.graph.ExceptionalUnitGraph;
import soot.toolkits.graph.ExceptionalUnitGraph.ExceptionDest;
import soot.toolkits.graph.MHGDominatorsFinder;
import soot.toolkits.graph.MHGPostDominatorsFinder;

/**
 * Interprocedural control-flow graph for the infoflow solver
 * 
 * @author Steven Arzt
 * @author Eric Bodden
 */
public class InfoflowCFG implements IInfoflowCFG {

	protected final static int MAX_SIDE_EFFECT_ANALYSIS_DEPTH = 25;
	protected final static int MAX_STATIC_USE_ANALYSIS_DEPTH = 50;

	public static enum StaticFieldUse {
		Unknown, Unused, Read, Write, ReadWrite;

		public StaticFieldUse merge(StaticFieldUse p) {
			if ((this == Read && p == Write) || (this == Write && p == Read))
				return ReadWrite;
			if (this == ReadWrite || p == ReadWrite)
				return ReadWrite;
			if (p == this)
				return this;
			if (p == Unused)
				return this;
			if (this == Unused)
				return p;
			return Unknown;
		}
	}

	protected final Map> staticFieldUses = new ConcurrentHashMap>();
	protected final Map methodSideEffects = new ConcurrentHashMap();

	protected final BiDiInterproceduralCFG delegate;

	protected final LoadingCache> unitsToDominatorFinder = IDESolver.DEFAULT_CACHE_BUILDER
			.build(new CacheLoader>() {
				@Override
				public MHGDominatorsFinder load(Unit unit) throws Exception {
					SootMethod method = getMethodOf(unit);
					DirectedGraph graph = delegate.getOrCreateUnitGraph(method);
					return new MHGDominatorsFinder<>(graph);
				}
			});

	protected final LoadingCache unitsToDominator = IDESolver.DEFAULT_CACHE_BUILDER
			.build(new CacheLoader() {
				@Override
				public UnitContainer load(Unit unit) throws Exception {
					SootMethod method = getMethodOf(unit);
					DirectedGraph graph = delegate.getOrCreateUnitGraph(method);

					MHGDominatorsFinder dominatorFinder = new MHGDominatorsFinder(graph);
					Unit dom = dominatorFinder.getImmediateDominator(unit);
					if (dom == null)
						return new UnitContainer(method);
					else
						return new UnitContainer(dom);
				}
			});

	protected final LoadingCache unitToPostdominator = IDESolver.DEFAULT_CACHE_BUILDER
			.build(new CacheLoader() {
				@Override
				public UnitContainer load(Unit unit) throws Exception {
					SootMethod method = getMethodOf(unit);
					DirectedGraph graph = delegate.getOrCreateUnitGraph(method);

					MHGPostDominatorsFinder postdominatorFinder = new MHGPostDominatorsFinder(graph);
					Unit postdom = postdominatorFinder.getImmediateDominator(unit);
					if (postdom == null)
						return new UnitContainer(method);
					else
						return new UnitContainer(postdom);
				}
			});

	protected final LoadingCache methodToUsedLocals = IDESolver.DEFAULT_CACHE_BUILDER
			.build(new CacheLoader() {
				@Override
				public Local[] load(SootMethod method) throws Exception {
					if (!method.isConcrete() || !method.hasActiveBody())
						return new Local[0];

					List lcs = new ArrayList(method.getParameterCount() + (method.isStatic() ? 0 : 1));

					for (Unit u : method.getActiveBody().getUnits())
						useBox: for (ValueBox vb : u.getUseBoxes()) {
							// Check for parameters
							for (int i = 0; i < method.getParameterCount(); i++) {
								if (method.getActiveBody().getParameterLocal(i) == vb.getValue()) {
									lcs.add((Local) vb.getValue());
									continue useBox;
								}
							}
						}

					// Add the "this" local
					if (!method.isStatic())
						lcs.add(method.getActiveBody().getThisLocal());

					return lcs.toArray(new Local[lcs.size()]);
				}
			});

	protected final LoadingCache methodToWrittenLocals = IDESolver.DEFAULT_CACHE_BUILDER
			.build(new CacheLoader() {
				@Override
				public Local[] load(SootMethod method) throws Exception {
					if (!method.isConcrete() || !method.hasActiveBody())
						return new Local[0];

					List lcs = new ArrayList(method.getActiveBody().getLocalCount());

					for (Unit u : method.getActiveBody().getUnits())
						if (u instanceof AssignStmt) {
							AssignStmt assignStmt = (AssignStmt) u;
							if (assignStmt.getLeftOp() instanceof Local)
								lcs.add((Local) assignStmt.getLeftOp());
						}

					return lcs.toArray(new Local[lcs.size()]);
				}
			});

	public InfoflowCFG() {
		this(new JimpleBasedInterproceduralCFG(true, true));
	}

	public InfoflowCFG(BiDiInterproceduralCFG delegate) {
		this.delegate = delegate;
	}

	@Override
	public UnitContainer getPostdominatorOf(Unit u) {
		return unitToPostdominator.getUnchecked(u);
	}

	@Override
	public UnitContainer getDominatorOf(Unit u) {
		return unitsToDominator.getUnchecked(u);
	}

	@Override
	public List getAllDominators(Unit u) {
		return unitsToDominatorFinder.getUnchecked(u).getDominators(u);
	}

	// delegate methods follow

	@Override
	public SootMethod getMethodOf(Unit u) {
		if (u == null)
			throw new RuntimeException("Cannot get the method that contains a null unit");
		return delegate.getMethodOf(u);
	}

	@Override
	public List getSuccsOf(Unit u) {
		return delegate.getSuccsOf(u);
	}

	@Override
	public boolean isExitStmt(Unit u) {
		return delegate.isExitStmt(u);
	}

	@Override
	public boolean isStartPoint(Unit u) {
		return delegate.isStartPoint(u);
	}

	@Override
	public boolean isFallThroughSuccessor(Unit u, Unit succ) {
		return delegate.isFallThroughSuccessor(u, succ);
	}

	@Override
	public boolean isBranchTarget(Unit u, Unit succ) {
		return delegate.isBranchTarget(u, succ);
	}

	@Override
	public Collection getStartPointsOf(SootMethod m) {
		return delegate.getStartPointsOf(m);
	}

	@Override
	public boolean isCallStmt(Unit u) {
		return delegate.isCallStmt(u);
	}

	@Override
	public Set allNonCallStartNodes() {
		return delegate.allNonCallStartNodes();
	}

	@Override
	public Collection getCalleesOfCallAt(Unit u) {
		return delegate.getCalleesOfCallAt(u);
	}

	@Override
	public Collection getCallersOf(SootMethod m) {
		return delegate.getCallersOf(m);
	}

	@Override
	public Collection getReturnSitesOfCallAt(Unit u) {
		return delegate.getReturnSitesOfCallAt(u);
	}

	@Override
	public Set getCallsFromWithin(SootMethod m) {
		return delegate.getCallsFromWithin(m);
	}

	@Override
	public List getPredsOf(Unit u) {
		return delegate.getPredsOf(u);
	}

	@Override
	public Collection getEndPointsOf(SootMethod m) {
		return delegate.getEndPointsOf(m);
	}

	@Override
	public List getPredsOfCallAt(Unit u) {
		return delegate.getPredsOf(u);
	}

	@Override
	public Set allNonCallEndNodes() {
		return delegate.allNonCallEndNodes();
	}

	@Override
	public DirectedGraph getOrCreateUnitGraph(SootMethod m) {
		return delegate.getOrCreateUnitGraph(m);
	}

	@Override
	public List getParameterRefs(SootMethod m) {
		return delegate.getParameterRefs(m);
	}

	@Override
	public boolean isReturnSite(Unit n) {
		return delegate.isReturnSite(n);
	}

	@Override
	public boolean isStaticFieldRead(SootMethod method, SootField variable) {
		StaticFieldUse use = checkStaticFieldUsed(method, variable);
		return use == StaticFieldUse.Read || use == StaticFieldUse.ReadWrite || use == StaticFieldUse.Unknown;
	}

	@Override
	public boolean isStaticFieldUsed(SootMethod method, SootField variable) {
		StaticFieldUse use = checkStaticFieldUsed(method, variable);
		return use == StaticFieldUse.Write || use == StaticFieldUse.ReadWrite || use == StaticFieldUse.Unknown;
	}

	protected StaticFieldUse checkStaticFieldUsed(SootMethod smethod, SootField variable) {
		// Skip over phantom methods
		if (!smethod.isConcrete() || !smethod.hasActiveBody())
			return StaticFieldUse.Unused;

		// Since we process at max MAX_STATIC_USE_ANALYSIS_DEPTH, we should use that
		// as a maximum.
		List workList = new ArrayList<>(MAX_STATIC_USE_ANALYSIS_DEPTH);
		workList.add(smethod);
		Map tempUses = new HashMap<>();

		int processedMethods = 0;
		while (!workList.isEmpty()) {
			// DFS: We need to be able post-process a method once we know what all the
			// invocations do
			SootMethod method = workList.remove(workList.size() - 1);
			processedMethods++;

			// Without a body, we cannot say much
			if (!method.hasActiveBody())
				continue;

			// Limit the maximum analysis depth
			if (processedMethods > MAX_STATIC_USE_ANALYSIS_DEPTH)
				return StaticFieldUse.Unknown;

			boolean hasInvocation = false;
			boolean reads = false, writes = false;

			// Do we already have a cache entry?
			Map entry = staticFieldUses.get(method);
			if (entry != null) {
				StaticFieldUse b = entry.get(variable);
				if (b != null && b != StaticFieldUse.Unknown) {
					tempUses.put(method, b);
					continue;
				}
			}

			// Do we already have an entry?
			StaticFieldUse oldUse = tempUses.get(method);

			// Scan for references to this variable
			for (Unit u : method.getActiveBody().getUnits()) {
				if (u instanceof AssignStmt) {
					AssignStmt assign = (AssignStmt) u;

					if (assign.getLeftOp() instanceof StaticFieldRef) {
						SootField sf = ((StaticFieldRef) assign.getLeftOp()).getField();
						registerStaticVariableUse(method, sf, StaticFieldUse.Write);
						if (variable.equals(sf))
							writes = true;
					}

					if (assign.getRightOp() instanceof StaticFieldRef) {
						SootField sf = ((StaticFieldRef) assign.getRightOp()).getField();
						registerStaticVariableUse(method, sf, StaticFieldUse.Read);
						if (variable.equals(sf))
							reads = true;
					}
				}

				if (((Stmt) u).containsInvokeExpr())
					for (Iterator edgeIt = Scene.v().getCallGraph().edgesOutOf(u); edgeIt.hasNext();) {
						Edge e = edgeIt.next();
						SootMethod callee = e.getTgt().method();
						if (callee.isConcrete()) {
							// Do we already know this method?
							StaticFieldUse calleeUse = tempUses.get(callee);
							if (calleeUse == null) {
								// We need to get back to the current method after we have processed the callees
								if (!hasInvocation)
									workList.add(method);

								// Process the callee
								workList.add(callee);
								hasInvocation = true;
							} else {
								reads |= calleeUse == StaticFieldUse.Read || calleeUse == StaticFieldUse.ReadWrite;
								writes |= calleeUse == StaticFieldUse.Write || calleeUse == StaticFieldUse.ReadWrite;
							}
						}
					}
			}

			// Variable is not read
			StaticFieldUse fieldUse = StaticFieldUse.Unused;
			if (reads && writes)
				fieldUse = StaticFieldUse.ReadWrite;
			else if (reads)
				fieldUse = StaticFieldUse.Read;
			else if (writes)
				fieldUse = StaticFieldUse.Write;

			// Have we changed our previous state?
			if (fieldUse == oldUse)
				continue;
			tempUses.put(method, fieldUse);
		}

		// Merge the temporary results into the global cache
		synchronized (tempUses) {
			for (Entry tempEntry : tempUses.entrySet()) {
				registerStaticVariableUse(tempEntry.getKey(), variable, tempEntry.getValue());
			}
		}

		StaticFieldUse outerUse = tempUses.get(smethod);
		return outerUse == null ? StaticFieldUse.Unknown : outerUse;
	}

	protected void registerStaticVariableUse(SootMethod method, SootField variable, StaticFieldUse fieldUse) {
		Map entry = staticFieldUses.get(method);
		StaticFieldUse oldUse;
		synchronized (staticFieldUses) {
			if (entry == null) {
				entry = new ConcurrentHashMap();
				staticFieldUses.put(method, entry);
				entry.put(variable, fieldUse);
				return;
			}

			oldUse = entry.get(variable);
			if (oldUse == null) {
				entry.put(variable, fieldUse);
				return;
			}
		}

		// This part is monotonic, so no need for synchronization
		StaticFieldUse newUse;
		switch (oldUse) {
		case Unknown:
		case Unused:
		case ReadWrite:
			newUse = fieldUse;
			break;
		case Read:
			newUse = (fieldUse == StaticFieldUse.Read) ? oldUse : StaticFieldUse.ReadWrite;
			break;
		case Write:
			newUse = (fieldUse == StaticFieldUse.Write) ? oldUse : StaticFieldUse.ReadWrite;
			break;
		default:
			throw new RuntimeException("Invalid field use");
		}
		entry.put(variable, newUse);
	}

	@Override
	public boolean hasSideEffects(SootMethod method) {
		return hasSideEffects(method, new HashSet(), 0);
	}

	protected boolean hasSideEffects(SootMethod method, Set runList, int depth) {
		// Without a body, we cannot say much
		if (!method.hasActiveBody())
			return false;

		// Do not process the same method twice
		if (!runList.add(method))
			return false;

		// Do we already have an entry?
		Boolean hasSideEffects = methodSideEffects.get(method);
		if (hasSideEffects != null)
			return hasSideEffects;

		// Limit the maximum analysis depth
		if (depth > MAX_SIDE_EFFECT_ANALYSIS_DEPTH)
			return true;

		// Scan for references to this variable
		for (Unit u : method.getActiveBody().getUnits()) {
			if (u instanceof AssignStmt) {
				AssignStmt assign = (AssignStmt) u;

				if (assign.getLeftOp() instanceof FieldRef) {
					methodSideEffects.put(method, true);
					return true;
				}
			}

			if (((Stmt) u).containsInvokeExpr())
				for (Iterator edgeIt = Scene.v().getCallGraph().edgesOutOf(u); edgeIt.hasNext();) {
					Edge e = edgeIt.next();
					if (hasSideEffects(e.getTgt().method(), runList, depth++))
						return true;
				}
		}

		// Variable is not read
		methodSideEffects.put(method, false);
		return false;
	}

	@Override
	public void notifyMethodChanged(SootMethod m) {
		if (delegate instanceof JimpleBasedInterproceduralCFG)
			((JimpleBasedInterproceduralCFG) delegate).initializeUnitToOwner(m);
	}

	@Override
	public void notifyNewBody(Body b) {
		if (delegate instanceof JimpleBasedInterproceduralCFG)
			((JimpleBasedInterproceduralCFG) delegate).initializeUnitToOwner(b);
	}

	@Override
	public boolean methodReadsValue(SootMethod m, Value v) {
		Local[] reads = methodToUsedLocals.getUnchecked(m);
		if (reads != null)
			for (Local l : reads)
				if (l == v)
					return true;
		return false;
	}

	@Override
	public boolean methodWritesValue(SootMethod m, Value v) {
		Local[] writes = methodToWrittenLocals.getUnchecked(m);
		if (writes != null)
			for (Local l : writes)
				if (l == v)
					return true;
		return false;
	}

	@Override
	public boolean isExceptionalEdgeBetween(Unit u1, Unit u2) {
		SootMethod m1 = getMethodOf(u1);
		SootMethod m2 = getMethodOf(u2);
		if (m1 != m2)
			throw new RuntimeException("Exceptional edges are only supported inside the same method");
		DirectedGraph ug1 = getOrCreateUnitGraph(m1);

		// Exception tracking might be disabled
		if (!(ug1 instanceof ExceptionalUnitGraph))
			return false;

		ExceptionalUnitGraph eug = (ExceptionalUnitGraph) ug1;
		if (!eug.getExceptionalSuccsOf(u1).contains(u2))
			return false;

		// The ExceptionalUnitGraph has edges from the predecessors of thrower
		// statements to the respective catch block to model that the predecessor was
		// potentially the last statement to be fully executed before arriving at the
		// catch block. For our purposes, we don't want that edge, because there the
		// thrower itself is at least attempted to be executed, before we end up in the
		// exception handler.
		Collection dests = eug.getExceptionDests(u1);
		if (dests != null && !dests.isEmpty()) {
			ThrowableSet ts = Scene.v().getDefaultThrowAnalysis().mightThrow(u1);
			if (ts != null) {
				boolean hasTraps = false;
				for (ExceptionDest dest : dests) {
					Trap trap = dest.getTrap();
					if (trap != null) {
						hasTraps = true;
						if (!ts.catchableAs(trap.getException().getType()))
							return false;
					}
				}
				if (!hasTraps)
					return false;
			}
		}
		return true;
	}

	@Override
	public List getConditionalBranchIntraprocedural(Unit callSite) {
		return null;
	}

	@Override
	public List getConditionalBranchesInterprocedural(Unit unit) {
		return null;
	}

	@Override
	public boolean isReachable(Unit u) {
		return delegate.isReachable(u);
	}

	@Override
	public boolean isExecutorExecute(InvokeExpr ie, SootMethod dest) {
		if (ie == null || dest == null)
			return false;

		SootMethod ieMethod = ie.getMethod();
		if (!ieMethod.getName().equals("execute") && !ieMethod.getName().equals("doPrivileged"))
			return false;

		final String ieSubSig = ieMethod.getSubSignature();
		final String calleeSubSig = dest.getSubSignature();

		if (ieSubSig.equals("void execute(java.lang.Runnable)") && calleeSubSig.equals("void run()"))
			return true;

		if (dest.getName().equals("run") && dest.getParameterCount() == 0 && dest.getReturnType() instanceof RefType) {
			if (ieSubSig.equals("java.lang.Object doPrivileged(java.security.PrivilegedAction)"))
				return true;
			if (ieSubSig.equals("java.lang.Object doPrivileged(java.security.PrivilegedAction,"
					+ "java.security.AccessControlContext)"))
				return true;
			if (ieSubSig.equals("java.lang.Object doPrivileged(java.security.PrivilegedExceptionAction)"))
				return true;
			if (ieSubSig.equals("java.lang.Object doPrivileged(java.security.PrivilegedExceptionAction,"
					+ "java.security.AccessControlContext)"))
				return true;
		}
		return false;
	}

	@Override
	public Collection getOrdinaryCalleesOfCallAt(Unit u) {
		InvokeExpr iexpr = ((Stmt) u).getInvokeExpr();

		Collection originalCallees = getCalleesOfCallAt(u);
		List callees = new ArrayList<>(originalCallees.size());
		for (SootMethod sm : originalCallees)
			if (!sm.isStaticInitializer() && !isExecutorExecute(iexpr, sm))
				callees.add(sm);
		return callees;
	}

	@Override
	public boolean isReflectiveCallSite(Unit u) {
		if (isCallStmt(u)) {
			InvokeExpr iexpr = ((Stmt) u).getInvokeExpr();
			return isReflectiveCallSite(iexpr);
		}
		return false;
	}

	@Override
	public boolean isReflectiveCallSite(InvokeExpr iexpr) {
		if (iexpr instanceof VirtualInvokeExpr) {
			VirtualInvokeExpr viexpr = (VirtualInvokeExpr) iexpr;
			if (viexpr.getBase().getType() instanceof RefType)
				if (((RefType) viexpr.getBase().getType()).getSootClass().getName().equals("java.lang.reflect.Method"))
					if (viexpr.getMethod().getName().equals("invoke"))
						return true;
		}
		return false;
	}

	@Override
	public void purge() {
		methodSideEffects.clear();
		staticFieldUses.clear();

		methodToUsedLocals.invalidateAll();
		methodToUsedLocals.cleanUp();

		methodToWrittenLocals.invalidateAll();
		methodToWrittenLocals.cleanUp();

		unitToPostdominator.invalidateAll();
		unitToPostdominator.cleanUp();

		unitsToDominator.invalidateAll();
		unitsToDominator.cleanUp();
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy