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

soot.jimple.infoflow.methodSummary.taintWrappers.SummaryTaintWrapper Maven / Gradle / Ivy

package soot.jimple.infoflow.methodSummary.taintWrappers;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import heros.solver.Pair;
import heros.solver.PathEdge;
import soot.ArrayType;
import soot.FastHierarchy;
import soot.Hierarchy;
import soot.Local;
import soot.MethodSubSignature;
import soot.Modifier;
import soot.PointsToAnalysis;
import soot.PointsToSet;
import soot.PrimType;
import soot.RefType;
import soot.Scene;
import soot.SootClass;
import soot.SootField;
import soot.SootFieldRef;
import soot.SootMethod;
import soot.SootMethodRef;
import soot.Type;
import soot.Unit;
import soot.Value;
import soot.VoidType;
import soot.jimple.DefinitionStmt;
import soot.jimple.DynamicInvokeExpr;
import soot.jimple.InstanceInvokeExpr;
import soot.jimple.InvokeExpr;
import soot.jimple.ReturnStmt;
import soot.jimple.StaticInvokeExpr;
import soot.jimple.Stmt;
import soot.jimple.infoflow.InfoflowConfiguration;
import soot.jimple.infoflow.InfoflowManager;
import soot.jimple.infoflow.collections.ICollectionsSupport;
import soot.jimple.infoflow.collections.context.UnknownContext;
import soot.jimple.infoflow.collections.strategies.containers.DefaultConfigContainerStrategyFactory;
import soot.jimple.infoflow.collections.strategies.containers.IContainerStrategy;
import soot.jimple.infoflow.collections.strategies.containers.IContainerStrategyFactory;
import soot.jimple.infoflow.collections.util.NonNullHashSet;
import soot.jimple.infoflow.collections.util.Tristate;
import soot.jimple.infoflow.data.Abstraction;
import soot.jimple.infoflow.data.AccessPath;
import soot.jimple.infoflow.data.AccessPath.ArrayTaintType;
import soot.jimple.infoflow.data.ContainerContext;
import soot.jimple.infoflow.data.SootMethodAndClass;
import soot.jimple.infoflow.handlers.PreAnalysisHandler;
import soot.jimple.infoflow.methodSummary.data.provider.IMethodSummaryProvider;
import soot.jimple.infoflow.methodSummary.data.sourceSink.AbstractFlowSinkSource;
import soot.jimple.infoflow.methodSummary.data.sourceSink.ConstraintType;
import soot.jimple.infoflow.methodSummary.data.sourceSink.FlowConstraint;
import soot.jimple.infoflow.methodSummary.data.sourceSink.FlowSink;
import soot.jimple.infoflow.methodSummary.data.sourceSink.FlowSource;
import soot.jimple.infoflow.methodSummary.data.summary.AbstractMethodSummary;
import soot.jimple.infoflow.methodSummary.data.summary.ClassMethodSummaries;
import soot.jimple.infoflow.methodSummary.data.summary.ClassSummaries;
import soot.jimple.infoflow.methodSummary.data.summary.GapDefinition;
import soot.jimple.infoflow.methodSummary.data.summary.MethodClear;
import soot.jimple.infoflow.methodSummary.data.summary.MethodFlow;
import soot.jimple.infoflow.methodSummary.data.summary.MethodSummaries;
import soot.jimple.infoflow.methodSummary.data.summary.SourceSinkType;
import soot.jimple.infoflow.methodSummary.data.summary.SummaryMetaData;
import soot.jimple.infoflow.methodSummary.taintWrappers.resolvers.SummaryQuery;
import soot.jimple.infoflow.methodSummary.taintWrappers.resolvers.SummaryResolver;
import soot.jimple.infoflow.methodSummary.taintWrappers.resolvers.SummaryResponse;
import soot.jimple.infoflow.solver.EndSummary;
import soot.jimple.infoflow.solver.IFollowReturnsPastSeedsHandler;
import soot.jimple.infoflow.taintWrappers.IReversibleTaintWrapper;
import soot.jimple.infoflow.taintWrappers.ITaintPropagationWrapper;
import soot.jimple.infoflow.typing.TypeUtils;
import soot.jimple.infoflow.util.ByReferenceBoolean;
import soot.jimple.infoflow.util.SootMethodRepresentationParser;
import soot.jimple.infoflow.util.SystemClassHandler;
import soot.util.ConcurrentHashMultiMap;
import soot.util.MultiMap;

/**
 * Taint wrapper implementation that applies method summaries created by
 * StubDroid
 * 
 * @author Steven Arzt
 *
 */
public class SummaryTaintWrapper implements IReversibleTaintWrapper, ICollectionsSupport {

	protected InfoflowManager manager;
	private AtomicInteger wrapperHits = new AtomicInteger();
	private AtomicInteger wrapperMisses = new AtomicInteger();
	private boolean reportMissingSummaries = false;
	protected ITaintPropagationWrapper fallbackWrapper = null;

	protected IMethodSummaryProvider flows;

	private Hierarchy hierarchy;
	private FastHierarchy fastHierarchy;
	private SummaryResolver summaryResolver;

	protected MultiMap, AccessPathPropagator> userCodeTaints = new ConcurrentHashMultiMap<>();

	protected IContainerStrategy containerStrategy;
	protected IContainerStrategyFactory containerStrategyFactory;

	/**
	 * Handler that is used for injecting taints from callbacks implemented in user
	 * code back into the summary application process
	 * 
	 * @author Steven Arzt
	 * 
	 */
	private class SummaryFRPSHandler implements IFollowReturnsPastSeedsHandler {

		@Override
		public void handleFollowReturnsPastSeeds(Abstraction d1, Unit u, Abstraction d2) {
			// We first check whether we return from a gap into which we have previous
			// descended
			SootMethod sm = manager.getICFG().getMethodOf(u);
			Set propagators = getUserCodeTaints(d1, sm);
			if (propagators != null && !propagators.isEmpty())
				handleFlowBackFromGap(u, d2, propagators);
			else
				handleFlowSourceInGap(u, d2);
		}

		/**
		 * Handles the case in which the source of the data flow was located inside a
		 * gap and the FRPS case now injects the taint into the analysis for the first
		 * time
		 * 
		 * @param u  The return statement inside the gap
		 * @param d2 The abstraction at the return statement inside the gap
		 */
		protected void handleFlowSourceInGap(Unit u, Abstraction d2) {
			final boolean reverseFlows = manager.getConfig()
					.getDataFlowDirection() == InfoflowConfiguration.DataFlowDirection.Backwards;

			// We can only handle an FRPS case if the taint actually leaves the method
			boolean taintLeavesMethod = false;
			SootMethod callee = manager.getICFG().getMethodOf(u);
			if (reverseFlows) {
				taintLeavesMethod = callee.getActiveBody().getParameterLocals()
						.contains(d2.getAccessPath().getPlainValue());
			} else {
				if (u instanceof ReturnStmt) {
					ReturnStmt retStmt = (ReturnStmt) u;
					taintLeavesMethod = retStmt.getOp() == d2.getAccessPath().getPlainValue();
				}
			}
			if (!taintLeavesMethod)
				return;

			for (Unit callSite : manager.getICFG().getCallersOf(callee)) {
				Stmt sCallSite = (Stmt) callSite;
				ClassSummaries summaries = getFlowSummariesForMethod(sCallSite, sCallSite.getInvokeExpr().getMethod(),
						null);

				List worklist = new ArrayList<>();
				for (MethodFlow flow : summaries.getAllFlows()) {
					FlowSource src = flow.source();
					FlowSink tgt = flow.sink();

					boolean flowMatchesTaint = false;
					if (!reverseFlows && src.isReturn() && src.hasGap()
							&& isImplementationOf(callee, src.getGap().getSignature())) {
						// We have a flow from the return value of the gap to somewhere
						flowMatchesTaint = true;
					} else if (reverseFlows && tgt.isParameter() && tgt.hasGap()
							&& isImplementationOf(callee, tgt.getGap().getSignature())) {
						// We have a backward flow to a parameter
						flowMatchesTaint = true;
					}
					if (!flowMatchesTaint)
						continue;

					// Create the taint from the incoming access path
					Set returnTaints = createTaintFromAccessPathOnReturn(d2.getAccessPath(), (Stmt) u,
							reverseFlows ? tgt.getGap() : src.getGap());
					if (returnTaints == null)
						continue;

					// Create the new propagator, one for every taint
					for (Taint returnTaint : returnTaints) {
						AccessPathPropagator propagator = new AccessPathPropagator(returnTaint, null, null, sCallSite,
								manager.getMainSolver().getTabulationProblem().zeroValue(), d2);
						if (reverseFlows)
							propagator = propagator.deriveInversePropagator();
						worklist.add(propagator);
					}
				}

				if (!worklist.isEmpty()) {
					Set newAPs = applyFlowsIterative(summaries.getMergedMethodSummaries(), worklist,
							reverseFlows, sCallSite, d2, true);
					if (newAPs != null && !newAPs.isEmpty()) {
						Abstraction zeroValue = manager.getMainSolver().getTabulationProblem().zeroValue();
						for (AccessPath ap : newAPs) {
							Abstraction abs = d2.deriveNewAbstraction(ap, reverseFlows ? sCallSite : (Stmt) u);
							if (reverseFlows)
								abs.setCorrespondingCallSite(sCallSite);
							for (Unit succUnit : manager.getICFG().getSuccsOf(callSite))
								manager.getMainSolver()
										.processEdge(new PathEdge(zeroValue, succUnit, abs));
						}
					}
				}
			}
		}

		/**
		 * Handles data flows that were originally passed into a gap, have been
		 * propagated through the gap, and must now return to user code
		 * 
		 * @param u           The return statement in the gap
		 * @param d2          The abstraction the return statement in the gap
		 * @param propagators The propagators from which the taint propagation can
		 *                    continue after the gap
		 */
		protected void handleFlowBackFromGap(Unit u, Abstraction d2, Set propagators) {
			final boolean reverseFlows = manager.getConfig()
					.getDataFlowDirection() == InfoflowConfiguration.DataFlowDirection.Backwards;
			for (AccessPathPropagator propagator : propagators) {
				// Propagate these taints up. We leave the current gap
				AccessPathPropagator parent = safePopParent(propagator);
				GapDefinition parentGap = propagator.getParent() == null ? null : propagator.getParent().getGap();

				// Create taints from the abstractions
				Set returnTaints = createTaintFromAccessPathOnReturn(d2.getAccessPath(), (Stmt) u,
						propagator.getGap());
				if (returnTaints == null)
					continue;

				// Get the correct set of flows to apply
				MethodSummaries flowsInTarget = parentGap == null ? getFlowsInOriginalCallee(propagator)
						: getFlowSummariesForGap(parentGap);

				// Create the new propagator, one for every taint
				Set workSet = new HashSet<>();
				for (Taint returnTaint : returnTaints) {
					AccessPathPropagator newPropagator = new AccessPathPropagator(returnTaint, parentGap, parent,
							propagator.getParent() == null ? null : propagator.getParent().getStmt(),
							propagator.getParent() == null ? null : propagator.getParent().getD1(),
							propagator.getParent() == null ? null : propagator.getParent().getD2());
					workSet.add(newPropagator);
				}

				// Apply the aggregated propagators
				AccessPathPropagator rootPropagator = getOriginalCallSite(propagator);
				Set resultAPs = applyFlowsIterative(flowsInTarget, new ArrayList<>(workSet), reverseFlows,
						rootPropagator.getStmt(), d2, true);

				// Propagate the access paths
				if (resultAPs != null && !resultAPs.isEmpty()) {
					for (AccessPath ap : resultAPs) {
						Abstraction newAbs = rootPropagator.getD2().deriveNewAbstraction(ap, rootPropagator.getStmt());
						if (rootPropagator.isInversePropagator())
							newAbs.setCorrespondingCallSite(rootPropagator.getStmt());
						for (Unit succUnit : manager.getICFG().getSuccsOf(rootPropagator.getStmt()))
							manager.getMainSolver().processEdge(
									new PathEdge(rootPropagator.getD1(), succUnit, newAbs));
					}
				}
			}
		}

		/**
		 * Checks whether the given method may be an implementation of a callback method
		 * with the given signature
		 * 
		 * @param sm        The method that possibly implements the callbacks
		 * @param signature The signature of the original callback method
		 * @return True if the given method may be an implementation of the callback
		 *         with the given signature
		 */
		private boolean isImplementationOf(SootMethod sm, String signature) {
			SootMethodAndClass smac = SootMethodRepresentationParser.v().parseSootMethodString(signature);
			if (!smac.getMethodName().equals(sm.getName()))
				return false;
			if (smac.getParameters().size() != sm.getParameterCount())
				return false;

			// We perform a simple parameter check for now
			for (int i = 0; i < smac.getParameters().size(); i++) {
				if (!smac.getParameters().get(i).equals(sm.getParameterType(i).toString()))
					return false;
			}

			// The given method must be in a class that inherits the callback class
			SootClass callbackClass = Scene.v().getSootClassUnsafe(smac.getClassName());
			return Scene.v().getOrMakeFastHierarchy().canStoreClass(sm.getDeclaringClass(), callbackClass);
		}

		/**
		 * Gets the flows in the method that was originally called and from where the
		 * summary application was started
		 * 
		 * @param propagator A propagator somewhere in the call tree
		 * @return The summary flows inside the original callee
		 */
		private MethodSummaries getFlowsInOriginalCallee(AccessPathPropagator propagator) {
			Stmt originalCallSite = getOriginalCallSite(propagator).getStmt();

			// Get the flows in the original callee
			ClassSummaries flowsInCallee = getFlowSummariesForMethod(originalCallSite,
					originalCallSite.getInvokeExpr().getMethod(), null);
			// if (flowsInCallee.getClasses().size() != 1)
			// throw new RuntimeException("Original callee must only be one
			// method");

			String methodSig = originalCallSite.getInvokeExpr().getMethod().getSubSignature();
			return flowsInCallee.getAllSummariesForMethod(methodSig);
		}

		/**
		 * Gets the call site at which the taint application was originally started
		 * 
		 * @param propagator A propagator somewhere in the call tree
		 * @return The call site at which the taint application was originally started
		 *         if successful, otherwise null
		 */
		private AccessPathPropagator getOriginalCallSite(AccessPathPropagator propagator) {
			// Get the original call site
			AccessPathPropagator curProp = propagator;
			while (curProp != null) {
				if (curProp.getParent() == null)
					return curProp;
				curProp = curProp.getParent();
			}
			return null;
		}

	}

	/**
	 * Creates a new instance of the {@link SummaryTaintWrapper} class
	 * 
	 * @param flows The flows loaded from disk
	 */
	public SummaryTaintWrapper(IMethodSummaryProvider flows) {
		this.flows = flows;
		setContainerStrategyFactory(new DefaultConfigContainerStrategyFactory());
	}

	/**
	 * Sets the container strategy factory.
	 * 
	 * @param factory the new factory
	 * @return this object, use for builder pattern
	 */
	public SummaryTaintWrapper setContainerStrategyFactory(IContainerStrategyFactory factory) {
		this.containerStrategyFactory = factory;
		return this;
	}

	@Override
	public IContainerStrategy getContainerStrategy() {
		return containerStrategy;
	}

	@Override
	public void initialize(InfoflowManager manager) {
		this.manager = manager;
		if (containerStrategyFactory != null)
			this.containerStrategy = containerStrategyFactory.create(manager);

		// Load all classes for which we have summaries to signatures
		Set loadableClasses = flows.getAllClassesWithSummaries();
		if (loadableClasses != null) {
			for (String className : loadableClasses)
				loadClass(className);
		}

		for (String className : flows.getSupportedClasses())
			loadClass(className);

		// Initialize the resolver that decides which summary is applicable to which
		// call site
		this.summaryResolver = new SummaryResolver(flows);

		// Get the hierarchy
		final Scene scene = Scene.v();
		this.hierarchy = scene.getActiveHierarchy();
		this.fastHierarchy = scene.getOrMakeFastHierarchy();

		// Register the taint propagation handler
		manager.getMainSolver().setFollowReturnsPastSeedsHandler(new SummaryFRPSHandler());

		// If we have a fallback wrapper, we need to initialize that one as well
		if (fallbackWrapper != null)
			fallbackWrapper.initialize(manager);
	}

	class HierarchyInjector implements PreAnalysisHandler {
		@Override
		public void onBeforeCallgraphConstruction() {
			// Inject the hierarchy
			for (String className : flows.getAllClassesWithSummaries()) {
				SootClass sc = Scene.v().forceResolve(className, SootClass.SIGNATURES);
				if (!sc.isPhantom())
					continue;

				ClassMethodSummaries summaries = flows.getClassFlows(className);
				if (summaries == null)
					continue;

				// Some phantom classes are actually interfaces
				if (summaries.hasInterfaceInfo()) {
					if (summaries.isInterface())
						sc.setModifiers(sc.getModifiers() | Modifier.INTERFACE);
					else
						sc.setModifiers(sc.getModifiers() & ~Modifier.INTERFACE);
				}

				// Set the correct superclass
				if (summaries.hasSuperclass()) {
					final String superclassName = summaries.getSuperClass();
					SootClass scSuperclass = Scene.v().forceResolve(superclassName, SootClass.SIGNATURES);
					sc.setSuperclass(scSuperclass);
				}

				// Register the interfaces
				if (summaries.hasInterfaces()) {
					for (String intfName : summaries.getInterfaces()) {
						SootClass scIntf = Scene.v().forceResolve(intfName, SootClass.SIGNATURES);
						scIntf.setModifiers(sc.getModifiers() | Modifier.INTERFACE);
						if (!sc.implementsInterface(intfName))
							sc.addInterface(scIntf);
					}
				}
			}
		}

		@Override
		public void onAfterCallgraphConstruction() {
			// NO-OP
		}
	}

	public Collection getPreAnalysisHandlers() {
		return Collections.singleton(new HierarchyInjector());
	}

	/**
	 * Loads the class with the given name into the scene. This makes sure that
	 * there is at least a phantom class with the given name
	 * 
	 * @param className The name of the class to load
	 */
	protected void loadClass(String className) {
		SootClass sc = Scene.v().getSootClassUnsafe(className);
		if (sc == null) {
			sc = Scene.v().makeSootClass(className);
			sc.setPhantomClass();
			Scene.v().addClass(sc);
		} else if (sc.resolvingLevel() < SootClass.HIERARCHY)
			Scene.v().forceResolve(className, SootClass.HIERARCHY);
	}

	/**
	 * Creates a taint that can be propagated through method summaries based on the
	 * given access path. This method assumes that the given statement is a method
	 * call and that the access path is flowing into this call.
	 * 
	 * @param ap                  The access path from which to create a taint
	 * @param stmt                The statement at which the access path came in
	 * @param matchReturnedValues True if the method shall also match on the left
	 *                            side of assign statements that capture that return
	 *                            value of a method call, otherwise false
	 * @return The set of taints derived from the given access path
	 */
	protected Set createTaintFromAccessPathOnCall(AccessPath ap, Stmt stmt, boolean matchReturnedValues,
			ByReferenceBoolean killIncomingSource) {
		Value base = getMethodBase(stmt);
		Set newTaints = null;
		// Check whether the base object or some field in it is tainted
		if ((ap.isLocal() || ap.isInstanceFieldRef()) && base != null && base == ap.getPlainValue()) {
			if (newTaints == null)
				newTaints = new HashSet<>();

			newTaints.add(new Taint(SourceSinkType.Field, -1, ap.getBaseType().toString(), ap.getBaseContext(),
					new AccessPathFragment(ap), ap.getTaintSubFields()));
		}

		// Check whether a parameter is tainted
		int paramIdx = getParameterIndex(stmt, ap);
		if (paramIdx >= 0) {
			if (newTaints == null)
				newTaints = new HashSet<>();

			newTaints.add(new Taint(SourceSinkType.Parameter, paramIdx, ap.getBaseType().toString(),
					ap.getBaseContext(), new AccessPathFragment(ap), ap.getTaintSubFields()));
		}

		// If we also match returned values, we must do this here
		if (matchReturnedValues && stmt instanceof DefinitionStmt) {
			DefinitionStmt defStmt = (DefinitionStmt) stmt;
			if (defStmt.getLeftOp() == ap.getPlainValue()) {
				// retVals are killed backwards
				if (killIncomingSource != null)
					killIncomingSource.value = true;
				if (newTaints == null)
					newTaints = new HashSet<>();

				newTaints.add(new Taint(SourceSinkType.Return, -1, ap.getBaseType().toString(), ap.getBaseContext(),
						new AccessPathFragment(ap), ap.getTaintSubFields()));
			}
		}

		return newTaints;
	}

	/**
	 * Creates a set of taints that can be propagated through method summaries based
	 * on the given access path. This method assumes that the given statement is a
	 * return site from a method.
	 * 
	 * @param ap   The access path from which to create a taint
	 * @param stmt The statement at which the access path came in
	 * @param gap  The gap in which the taint is valid
	 * @return The taint derived from the given access path
	 */
	protected Set createTaintFromAccessPathOnReturn(AccessPath ap, Stmt stmt, GapDefinition gap) {
		SootMethod sm = manager.getICFG().getMethodOf(stmt);
		Set res = null;

		// Check whether the base object or some field in it is tainted
		if (!sm.isStatic() && (ap.isLocal() || ap.isInstanceFieldRef())
				&& ap.getPlainValue() == sm.getActiveBody().getThisLocal()) {
			if (res == null)
				res = new HashSet<>();
			res.add(new Taint(SourceSinkType.Field, -1, ap.getBaseType().toString(), ap.getBaseContext(),
					new AccessPathFragment(ap), ap.getTaintSubFields(), gap));
		}

		// Check whether a parameter is tainted
		int paramIdx = getParameterIndex(sm, ap);
		if (paramIdx >= 0) {
			if (res == null)
				res = new HashSet<>();
			res.add(new Taint(SourceSinkType.Parameter, paramIdx, ap.getBaseType().toString(), ap.getBaseContext(),
					new AccessPathFragment(ap), ap.getTaintSubFields(), gap));
		}

		// Check whether the return value is tainted
		if (stmt instanceof ReturnStmt) {
			ReturnStmt retStmt = (ReturnStmt) stmt;
			if (retStmt.getOp() == ap.getPlainValue()) {
				if (res == null)
					res = new HashSet<>();
				res.add(new Taint(SourceSinkType.Return, -1, ap.getBaseType().toString(), ap.getBaseContext(),
						new AccessPathFragment(ap), ap.getTaintSubFields(), gap));
			}
		}
		return res;
	}

	/**
	 * Converts a taint back into an access path that is valid at the given
	 * statement
	 * 
	 * @param t    The taint to convert into an access path
	 * @param stmt The statement at which the access path shall be valid
	 * @return The access path derived from the given taint
	 */
	protected AccessPath createAccessPathFromTaint(Taint t, Stmt stmt, boolean reverseFlows) {
		// Convert the taints to Soot objects
		SootField[] fields = safeGetFields(t.getAccessPath());
		Type[] types = safeGetTypes(t.getAccessPath(), fields);
		ContainerContext[][] contexts = safeGetContexts(t.getAccessPath());
		Type baseType = TypeUtils.getTypeFromString(t.getBaseType());
		ContainerContext[] baseContext = t.getBaseContext();
		soot.jimple.infoflow.data.AccessPathFragment fragments[] = soot.jimple.infoflow.data.AccessPathFragment
				.createFragmentArray(fields, types, contexts);

		// If the taint is a return value, we taint the left side of the
		// assignment
		if (t.isReturn()) {
			// If the return value is not used, we can abort
			if (!(stmt instanceof DefinitionStmt))
				return null;

			DefinitionStmt defStmt = (DefinitionStmt) stmt;
			return manager.getAccessPathFactory().createAccessPath(defStmt.getLeftOp(), baseType, baseContext,
					fragments, t.taintSubFields(), false, true, ArrayTaintType.ContentsAndLength, false);
		}

		// If the taint is a parameter value, we need to identify the
		// corresponding local
		if (t.isParameter() && stmt.containsInvokeExpr()) {
			InvokeExpr iexpr = stmt.getInvokeExpr();
			int paramIndex = t.getParameterIndex();

			// If the caller is missing a corresponding argument
			// then just quit here.
			// Note that this can happen, e.g., because of the android lifecycle model
			// where sometimes arguments/params get removed in native code which is
			// not represented in the call-graph.
			if (paramIndex >= iexpr.getArgCount())
				return null;

			Value paramVal = iexpr.getArg(paramIndex);
			if (!AccessPath.canContainValue(paramVal))
				return null;

			// If the target local's type does not agree with the precise type we have
			// computed, we just take the target local's type
			if (manager.getTypeUtils().getMorePreciseType(baseType, paramVal.getType()) == null)
				baseType = null;

			return manager.getAccessPathFactory().createAccessPath(paramVal, baseType, fragments, t.taintSubFields(),
					false, true, ArrayTaintType.ContentsAndLength);
		}

		// If the taint is on the base value, we need to taint the base local
		if (t.isField() && stmt.containsInvokeExpr()) {
			final InvokeExpr iexpr = stmt.getInvokeExpr();
			if (iexpr instanceof InstanceInvokeExpr) {
				InstanceInvokeExpr iiexpr = (InstanceInvokeExpr) iexpr;
				return manager.getAccessPathFactory().createAccessPath(iiexpr.getBase(), baseType, baseContext,
						fragments, t.taintSubFields(), false, true, ArrayTaintType.ContentsAndLength, false);
			} else if (iexpr instanceof StaticInvokeExpr) {
				// For a static invocation, we apply field taints to the return value
				StaticInvokeExpr siexpr = (StaticInvokeExpr) iexpr;
				if (!(siexpr.getMethodRef().getReturnType() instanceof VoidType)) {
					if (stmt instanceof DefinitionStmt) {
						DefinitionStmt defStmt = (DefinitionStmt) stmt;
						return manager.getAccessPathFactory().createAccessPath(defStmt.getLeftOp(), baseType,
								baseContext, fragments, t.taintSubFields(), false, true,
								ArrayTaintType.ContentsAndLength, false);
					} else
						return null;
				}
			}
		}

		throw new RuntimeException("Could not convert taint to access path: " + t + " at " + stmt);
	}

	/**
	 * Converts a taint into an access path that is valid inside a given method.
	 * This models that a taint is propagated into the method and from there on in
	 * normal IFDS.
	 * 
	 * @param t  The taint to convert
	 * @param sm The method in which the access path shall be created
	 * @return The access path derived from the given taint and method
	 */
	protected Set createAccessPathInMethod(Taint t, SootMethod sm) {
		// Convert the taints to Soot objects
		SootField[] fields = safeGetFields(t.getAccessPath());
		Type[] types = safeGetTypes(t.getAccessPath(), fields);
		Type baseType = TypeUtils.getTypeFromString(t.getBaseType());
		ContainerContext[] baseContext = t.getBaseContext();
		ContainerContext[][] contexts = safeGetContexts(t.getAccessPath());
		soot.jimple.infoflow.data.AccessPathFragment fragments[] = soot.jimple.infoflow.data.AccessPathFragment
				.createFragmentArray(fields, types, contexts);

		// A return value cannot be propagated into a method
		if (t.isReturn()) {
			if (manager.getConfig().getDataFlowDirection() == InfoflowConfiguration.DataFlowDirection.Forwards)
				throw new RuntimeException("Unsupported taint type");
			Set aps = new HashSet<>();
			for (Unit unit : sm.getActiveBody().getUnits()) {
				if (!(unit instanceof ReturnStmt))
					continue;

				AccessPath ap = manager.getAccessPathFactory().createAccessPath(((ReturnStmt) unit).getOp(), baseType,
						baseContext, fragments, t.taintSubFields(), false, true, ArrayTaintType.ContentsAndLength,
						false);
				aps.add(ap);
			}
			return aps;
		}

		if (t.isParameter()) {
			Local l = sm.getActiveBody().getParameterLocal(t.getParameterIndex());
			AccessPath ap = manager.getAccessPathFactory().createAccessPath(l, baseType, baseContext, fragments, true,
					false, true, ArrayTaintType.ContentsAndLength, false);
			return ap == null ? Collections.emptySet() : Collections.singleton(ap);
		}

		if (t.isField() || t.isGapBaseObject()) {
			Local l = sm.getActiveBody().getThisLocal();
			AccessPath ap = manager.getAccessPathFactory().createAccessPath(l, baseType, baseContext, fragments, true,
					false, true, ArrayTaintType.ContentsAndLength, false);
			return ap == null ? Collections.emptySet() : Collections.singleton(ap);
		}
		throw new RuntimeException("Failed to convert taint " + t);
	}

	@Override
	public Set getTaintsForMethod(Stmt stmt, Abstraction d1, Abstraction taintedAbs) {
		// We only care about method invocations
		if (!stmt.containsInvokeExpr())
			return Collections.singleton(taintedAbs);

		Set resAbs = null;
		ByReferenceBoolean killIncomingTaint = new ByReferenceBoolean(false);
		ByReferenceBoolean classSupported = new ByReferenceBoolean(false);

		// Compute the wrapper taints for the current method
		final InvokeExpr inv = stmt.getInvokeExpr();
		SootMethod callee = inv.getMethod();
		Set res;
		if (inv instanceof DynamicInvokeExpr) {
			final DynamicInvokeExpr dyn = (DynamicInvokeExpr) inv;
			SootMethod m = dyn.getBootstrapMethodRef().tryResolve();
			if (m == null)
				return null;
			callee = m;
		}
		res = computeTaintsForMethod(stmt, d1, taintedAbs, callee, killIncomingTaint, classSupported);
		boolean wasEqualToIncoming = false;

		// Create abstractions from the access paths
		if (res != null && !res.isEmpty()) {
			if (resAbs == null)
				resAbs = new HashSet<>();
			for (AccessPath ap : res) {
				resAbs.add(taintedAbs.deriveNewAbstraction(ap, stmt));
				if (ap.equals(taintedAbs.getAccessPath()))
					wasEqualToIncoming = true;
			}
		}

		// If we have no data flows, we can abort early
		if (!killIncomingTaint.value && (resAbs == null || resAbs.isEmpty())) {
			// Is this method explicitly excluded?
			if (!this.flows.isMethodExcluded(callee.getDeclaringClass().getName(), callee.getSubSignature())) {
				// wrapperMisses.incrementAndGet();

				if (classSupported.value)
					return Collections.singleton(taintedAbs);
				else {
					reportMissingSummary(callee, stmt, taintedAbs);
					return fallbackWrapper == null ? null : fallbackWrapper.getTaintsForMethod(stmt, d1, taintedAbs);
				}
			}
		}

		// We always retain the incoming abstraction unless it is explicitly
		// cleared
		if (!killIncomingTaint.value) {
			if (resAbs == null)
				return Collections.singleton(taintedAbs);
			if (!wasEqualToIncoming)
				resAbs.add(taintedAbs);
		}
		return resAbs;
	}

	/**
	 * Method that is called when no summary exists for a given method
	 * 
	 * @param method   The method for which no summary exists
	 * @param stmt     The statement that calls the method for which no summary
	 *                 exists
	 * @param incoming The incoming taint abstraction
	 */
	protected void reportMissingSummary(SootMethod method, Stmt stmt, Abstraction incoming) {
		reportMissingMethod(method);
	}

	/**
	 * Method that is called when no summary exists for a given method
	 * 
	 * @param method Method to be reported as missing
	 */
	protected void reportMissingMethod(SootMethod method) {
		if (reportMissingSummaries && SystemClassHandler.v().isClassInSystemPackage(method.getDeclaringClass()))
			System.out.println("Missing summary for class " + method.getDeclaringClass());
	}

	/**
	 * Computes library taints for the given method and incoming abstraction
	 * 
	 * @param stmt              The statement to which to apply the library summary
	 * @param d1                The context of the incoming taint
	 * @param taintedAbs        The incoming taint
	 * @param method            The method for which to get library model taints
	 * @param killIncomingTaint Outgoing value that defines whether the original
	 *                          taint shall be killed instead of being propagated
	 *                          onwards
	 * @param classSupported    Outgoing parameter that informs the caller whether
	 *                          the callee class is supported, i.e., there is a
	 *                          summary configuration for that class
	 * @return The artificial taints coming from the libary model if any, otherwise
	 *         null
	 */
	private Set computeTaintsForMethod(Stmt stmt, Abstraction d1, Abstraction taintedAbs,
			final SootMethod method, ByReferenceBoolean killIncomingTaint, ByReferenceBoolean classSupported) {
		// wrapperHits.incrementAndGet();

		// Get the cached data flows
		ClassSummaries flowsInCallees = getFlowSummariesForMethod(stmt, method, taintedAbs, classSupported);
		if (flowsInCallees == null || flowsInCallees.isEmpty())
			return null;

		// Create a level-0 propagator for the initially tainted access path
		Set taintsFromAP = createTaintFromAccessPathOnCall(taintedAbs.getAccessPath(), stmt, false, null);
		if (taintsFromAP == null || taintsFromAP.isEmpty())
			return null;

		Set res = null;
		for (String className : flowsInCallees.getClasses()) {
			// Get the flows in this class
			ClassMethodSummaries classFlows = flowsInCallees.getClassSummaries(className);
			if (classFlows == null || classFlows.isEmpty())
				continue;

			// Get the method-level flows
			MethodSummaries flowsInCallee = classFlows.getMethodSummaries();
			if (flowsInCallee == null || flowsInCallee.isEmpty())
				continue;

			// Check whether the incoming taint matches a clear
			List workList = new ArrayList();
			boolean preventPropagation = false;
			for (Taint taint : taintsFromAP) {
				boolean killTaint = false;
				if (killIncomingTaint != null && flowsInCallee.hasClears()) {
					for (MethodClear clear : flowsInCallee.getAllClears()) {
						if (clearMatchesTaint(clear, taint, stmt)) {
							killTaint = true;
							preventPropagation |= clear.preventPropagation();
							break;
						}
					}
				}

				if (killTaint)
					killIncomingTaint.value = true;
				if (!preventPropagation)
					workList.add(new AccessPathPropagator(taint, null, null, stmt, d1, taintedAbs));
			}

			// Apply the data flows until we reach a fixed point
			Set resCallee = applyFlowsIterative(flowsInCallee, workList, false, stmt, taintedAbs,
					killIncomingTaint.value);
			if (resCallee != null && !resCallee.isEmpty()) {
				if (res == null)
					res = new HashSet<>();
				res.addAll(resCallee);
			}
		}
		return res;
	}

	/**
	 * Iteratively applies all of the given flow summaries until a fixed point is
	 * reached. if the flow enters user code, an analysis of the corresponding
	 * method will be spawned.
	 *
	 * @param flowsInCallee     The flow summaries for the given callee
	 * @param workList          The incoming propagators on which to apply the flow
	 *                          summaries
	 * @param reverseFlows      True if flows should be applied reverse. Useful for
	 *                          back- wards analysis
	 * @param stmt
	 * @param incoming
	 * @param killIncomingTaint
	 * @return The set of outgoing access paths
	 */
	private Set applyFlowsIterative(MethodSummaries flowsInCallee, List workList,
			boolean reverseFlows, Stmt stmt, Abstraction incoming, boolean killIncomingTaint) {
		Set res = null;
		Set doneSet = new HashSet(workList);
		while (!workList.isEmpty()) {
			final AccessPathPropagator curPropagator = workList.remove(0);
			final GapDefinition curGap = curPropagator.getGap();
			// Make sure we don't have invalid data
			if (curGap != null && curPropagator.getParent() == null)
				throw new RuntimeException("Gap flow without parent detected");

			// Get the correct set of flows to apply
			MethodSummaries flowsInTarget;
			if (curGap == null) {
				// Not in a gap, obtain the flows for the callee
				flowsInTarget = flowsInCallee;
			} else {
				// We're in a gap. We first check whether we have summaries for the declared
				// target method of the gap
				flowsInTarget = getFlowSummariesForGap(curGap);

				// If we don't have flows for the declared gap method, we use the type
				// information from the call site to infer a target
				if (flowsInTarget == null || flowsInTarget.isEmpty()) {
					SootMethod gapMethod = Scene.v().grabMethod(curGap.getSignature());
					if (gapMethod != null) {
						SootMethod sm = manager.getICFG().getMethodOf(stmt);
						ClassSummaries targetSummaries = flowsInCallee.getInFlowsForGap(curGap).stream()
								.filter(f -> f.sink().isGapBaseObject()).map(f -> f.source().getBaseLocal(stmt))
								.flatMap(l -> rebaseSootMethod(gapMethod, l, sm)).filter(m -> m != null)
								.map(m -> getFlowSummariesForMethod(stmt, m, null))
								.collect(ClassSummaries::new, ClassSummaries::merge, ClassSummaries::merge);
						flowsInTarget = targetSummaries.getAllSummariesForMethod(curGap.getSignature());
					}
				}
			}

			// If we don't have summaries for the current gap, we look for
			// implementations in the application code
			if ((flowsInTarget == null || flowsInTarget.isEmpty()) && curGap != null) {
				SootMethod callee = Scene.v().grabMethod(curGap.getSignature());
				if (callee == null) {
					SootMethodAndClass smac = SootMethodRepresentationParser.v()
							.parseSootMethodString(curGap.getSignature());
					SootClass declClass = Scene.v().getSootClassUnsafe(smac.getClassName());
					if (declClass != null) {
						SootMethodRef ref = Scene.v().makeMethodRef(declClass, smac.getSubSignature(), false);
						callee = ref.tryResolve();
					}
				}
				if (callee != null) {
					for (SootMethod implementor : getImplementors(stmt, callee)) {
						Set implementorPropagators = spawnAnalysisIntoClientCode(implementor,
								curPropagator, stmt, incoming);
						if (implementorPropagators != null)
							workList.addAll(implementorPropagators);
					}
				}
			}

			// Apply the flow summaries for other libraries
			if (flowsInTarget != null && !flowsInTarget.isEmpty()) {
				if (reverseFlows)
					flowsInTarget = flowsInTarget.reverse();
				for (MethodFlow flow : flowsInTarget) {
					if (flow.isExcludedOnClear() && killIncomingTaint)
						continue;

					// Apply the flow summary
					AccessPathPropagator newPropagator = applyFlow(flow, curPropagator);
					if (newPropagator == null) {
						// Can we reverse the flow and apply it in the other direction?
						flow = getReverseFlowForAlias(flow, curPropagator.getTaint());
						if (flow == null)
							continue;

						// Apply the reversed flow
						newPropagator = applyFlow(flow, curPropagator);
						if (newPropagator == null)
							continue;
					}

					// Propagate it
					if (newPropagator.getParent() == null && newPropagator.getTaint().getGap() == null) {
						AccessPath ap = createAccessPathFromTaint(newPropagator.getTaint(), newPropagator.getStmt(),
								reverseFlows);
						if (ap == null)
							continue;
						else {
							if (res == null)
								res = new HashSet<>();
							res.add(ap);
						}
					}

					// Final flows signal that the flow itself is complete and does not need
					// another iteration to be correct. This allows to model things like return
					// the previously held value etc.
					if (doneSet.add(newPropagator) && !flow.isFinal())
						workList.add(newPropagator);

					// If we have have tainted a heap field, we need to look for
					// aliases as well
					if (newPropagator.getTaint().hasAccessPath() && !flow.isFinal()) {
						AccessPathPropagator backwardsPropagator = newPropagator.deriveInversePropagator();
						if (doneSet.add(backwardsPropagator))
							workList.add(backwardsPropagator);
					}
				}
			}
		}
		return res;
	}

	/**
	 * Finds the equivalent {@link SootMethod} in a derived class
	 * 
	 * @param targetMethod The original method
	 * @param baseLocal    The base local on which the method is invoked
	 * @param context      The method in which the target method is invoked
	 * @return The target method that is called
	 */
	public Stream rebaseSootMethod(SootMethod targetMethod, Local baseLocal, SootMethod context) {
		return Scene.v().getPointsToAnalysis().reachingObjects(baseLocal).possibleTypes().stream()
				.filter(t -> t instanceof RefType).map(t -> ((RefType) t).getSootClass())
				.map(sc -> Scene.v().getOrMakeFastHierarchy().resolveMethod(sc, targetMethod, false));
	}

	/**
	 * Checks whether the given flow denotes an aliasing relationship and can thus
	 * be applied in reverse
	 * 
	 * @param flow  The flow to check
	 * @param taint
	 * @return The reverse flow if the given flow works in both directions, null
	 *         otherwise
	 */
	protected MethodFlow getReverseFlowForAlias(MethodFlow flow, Taint taint) {
		// Reverse flows can only be applied if the flow is an
		// aliasing relationship
		if (!flow.isAlias(taint))
			return null;

		// Reverse flows can only be applied to heap objects
		if (!canTypeAlias(flow.source().getLastFieldType()))
			return null;
		if (!canTypeAlias(flow.sink().getLastFieldType()))
			return null;

		// There cannot be any flows to the return values of
		// gaps
		if (flow.source().getGap() != null && flow.source().getType() == SourceSinkType.Return)
			return null;
		if (flow.sink().getGap() != null && flow.sink().getType() == SourceSinkType.Return)
			return null;

		// If the flow manipulates a constraint, the outcome may be questionable
		if (flow.sink().getConstraintType().isAction())
			return null;

		// Reverse the flow if necessary
		return flow.reverse();
	}

	/**
	 * Checks whether objects of the given type can have aliases
	 * 
	 * @param type The type to check
	 * @return True if objects of the given type can have aliases, otherwise false
	 */
	protected boolean canTypeAlias(String type) {
		Type tp = TypeUtils.getTypeFromString(type);
		if (tp instanceof PrimType)
			return false;
		if (tp instanceof RefType)
			if (((RefType) tp).getClassName().equals("java.lang.String"))
				return false;
		return true;
	}

	/**
	 * Spawns the analysis into a gap implementation inside user code
	 *
	 * @param implementor The target method inside the user code into which the
	 *                    propagator shall be propagated
	 * @param propagator  The implementor that gets propagated into user code
	 * @return The taints at the end of the implementor method if a summary already
	 *         exists, otherwise false
	 */
	protected Set spawnAnalysisIntoClientCode(SootMethod implementor,
			AccessPathPropagator propagator, Stmt stmt, Abstraction incoming) {
		// If the implementor has not yet been loaded, we must do this now
		if (!implementor.hasActiveBody())
			implementor.retrieveActiveBody(body -> manager.getICFG().notifyNewBody(body));

		Set aps = createAccessPathInMethod(propagator.getTaint(), implementor);
		if (aps.isEmpty())
			return null;
		Set absSet = aps.stream().map(ap -> incoming.deriveNewAbstraction(ap, stmt))
				.filter(Objects::nonNull).collect(Collectors.toSet());

		// We need to pop the last gap element off the stack
		AccessPathPropagator parent = safePopParent(propagator);
		GapDefinition gap = propagator.getParent() == null ? null : propagator.getParent().getGap();

		// We might already have a summary for the callee
		Set outgoingTaints = null;
		for (Abstraction abs : absSet) {
			Set> endSummary = manager.getMainSolver().endSummary(implementor, abs);
			if (endSummary != null && !endSummary.isEmpty()) {
				for (EndSummary pair : endSummary) {
					if (outgoingTaints == null)
						outgoingTaints = new HashSet<>();

					// Create the taint that corresponds to the access path leaving
					// the user-code method
					Set newTaints = createTaintFromAccessPathOnReturn(pair.d4.getAccessPath(), (Stmt) pair.eP,
							propagator.getGap());
					if (newTaints != null) {
						for (Taint newTaint : newTaints) {
							AccessPathPropagator newPropagator = new AccessPathPropagator(newTaint, gap, parent,
									propagator.getParent() == null ? null : propagator.getParent().getStmt(),
									propagator.getParent() == null ? null : propagator.getParent().getD1(),
									propagator.getParent() == null ? null : propagator.getParent().getD2());
							outgoingTaints.add(newPropagator);
						}
					}
				}
				continue;
			}

			// Register the new context so that we can get the taints back
			this.userCodeTaints.put(new Pair<>(abs, implementor), propagator);

			// if we don't have a summary, we need to inject the taints into the solver
			for (Unit sP : manager.getICFG().getStartPointsOf(implementor)) {
				PathEdge edge = new PathEdge<>(abs, sP, abs);
				manager.getMainSolver().processEdge(edge);
			}
		}

		return outgoingTaints;
	}

	protected AccessPathPropagator safePopParent(AccessPathPropagator curPropagator) {
		if (curPropagator.getParent() == null)
			return null;
		return curPropagator.getParent().getParent();
	}

	/**
	 * Gets the flow summaries for the given gap definition, i.e., for the method in
	 * the gap
	 * 
	 * @param gap The gap definition
	 * @return The flow summaries for the method in the given gap if they exist,
	 *         otherwise null
	 */
	protected MethodSummaries getFlowSummariesForGap(GapDefinition gap) {
		// If we have the method in Soot, we can be more clever
		if (Scene.v().containsMethod(gap.getSignature())) {
			SootMethod gapMethod = Scene.v().getMethod(gap.getSignature());
			ClassSummaries flows = getFlowSummariesForMethod(null, gapMethod, null);
			if (flows != null && !flows.isEmpty()) {
				MethodSummaries summaries = new MethodSummaries();
				summaries.mergeSummaries(flows.getAllMethodSummaries());
				return summaries;
			}
		}

		// If we don't have the method, we can only directly look for the
		// signature
		SootMethodAndClass smac = SootMethodRepresentationParser.v().parseSootMethodString(gap.getSignature());
		ClassMethodSummaries cms = flows.getMethodFlows(smac.getClassName(), smac.getSubSignature());
		return cms == null ? null : cms.getMethodSummaries();
	}

	/**
	 * Gets the flow summaries for the given method
	 * 
	 * @param stmt           (Optional) The invocation statement at which the given
	 *                       method is called. If this parameter is not null, it is
	 *                       used to find further potential callees if there are no
	 *                       flow summaries for the given method.
	 * @param method         The method for which to get the flow summaries
	 * @param classSupported Outgoing parameter that informs the caller whether the
	 *                       callee class is supported, i.e., there is a summary
	 *                       configuration for that class
	 * @return The set of flow summaries for the given method if they exist,
	 *         otherwise null. Note that this is a set of sets, one set per possible
	 *         callee.
	 */
	private ClassSummaries getFlowSummariesForMethod(Stmt stmt, final SootMethod method,
			ByReferenceBoolean classSupported) {
		return getFlowSummariesForMethod(stmt, method, null, classSupported);
	}

	/**
	 * Gets the flow summaries for the given method
	 * 
	 * @param stmt           (Optional) The invocation statement at which the given
	 *                       method is called. If this parameter is not null, it is
	 *                       used to find further potential callees if there are no
	 *                       flow summaries for the given method.
	 * @param method         The method for which to get the flow summaries
	 * @param taintedAbs     The tainted incoming abstraction
	 * @param classSupported Outgoing parameter that informs the caller whether the
	 *                       callee class is supported, i.e., there is a summary
	 *                       configuration for that class
	 * @return The set of flow summaries for the given method if they exist,
	 *         otherwise null. Note that this is a set of sets, one set per possible
	 *         callee.
	 */
	protected ClassSummaries getFlowSummariesForMethod(Stmt stmt, final SootMethod method, Abstraction taintedAbs,
			ByReferenceBoolean classSupported) {
		final String subsig = method.getSubSignature();

		ClassSummaries classSummaries = null;
		SootClass morePreciseClass = getSummaryDeclaringClass(stmt,
				taintedAbs == null ? null : taintedAbs.getAccessPath());

		SummaryResponse response = summaryResolver
				.resolve(new SummaryQuery(morePreciseClass, method.getDeclaringClass(), subsig));
		if (response != null) {
			if (classSupported != null)
				classSupported.value = response.isClassSupported();
			classSummaries = new ClassSummaries();
			classSummaries.merge(response.getClassSummaries());
		}

		// Check the direct callee
		if (classSummaries == null || classSummaries.isEmpty()) {
			if (!method.isConstructor() && !method.isStaticInitializer() && !method.isStatic()) {
				// Check the callgraph
				if (stmt != null) {
					// Check the callees reported by the ICFG
					for (SootMethod callee : manager.getICFG().getCalleesOfCallAt(stmt)) {
						ClassMethodSummaries flows = this.flows.getMethodFlows(callee.getDeclaringClass(), subsig);
						if (flows != null && !flows.isEmpty()) {
							if (classSupported != null)
								classSupported.value = true;
							if (classSummaries == null)
								classSummaries = new ClassSummaries();
							classSummaries.merge("", flows.getMethodSummaries());
						}
					}
				}
			}
		}

		return classSummaries;
	}

	/**
	 * Gets the class that likely declares the method that is being called by the
	 * given statement
	 * 
	 * @param stmt      The invocation statement
	 * @param taintedAP The tainted access path
	 * @return The class in which to look for a summary for the called method
	 */
	protected SootClass getSummaryDeclaringClass(Stmt stmt, AccessPath taintedAP) {
		Type declaredType = null;
		if (stmt != null) {
			if (stmt.getInvokeExpr() instanceof InstanceInvokeExpr) {
				// If the base object of the call is tainted, we may have a more precise type in
				// the access path
				InstanceInvokeExpr iinv = (InstanceInvokeExpr) stmt.getInvokeExpr();
				if (taintedAP != null && iinv.getBase() == taintedAP.getPlainValue()) {
					declaredType = taintedAP.getBaseType();
				}

				// We may have a call such as
				// x = editable.toString();
				// In that case, the callee is Object.toString(), since in the stub Android
				// JAR, the class android.text.Editable does not override toString(). On a
				// real device, it does. Consequently, we have a summary in the "Editable"
				// class. To handle such weird cases, we walk the class hierarchy based on
				// the declared type of the base object.
				Type baseType = iinv.getBase().getType();
				declaredType = manager.getTypeUtils().getMorePreciseType(declaredType, baseType);
			} else if (stmt.getInvokeExpr() instanceof DynamicInvokeExpr) {
				return ((DynamicInvokeExpr) stmt.getInvokeExpr()).getBootstrapMethodRef().getDeclaringClass();

			}
		}
		return declaredType instanceof RefType ? ((RefType) declaredType).getSootClass() : null;
	}

	/**
	 * Gets suitable methods with an active body available being cast-compatible
	 * with method. First checks the call-graph for the given statement for suitable
	 * concrete callees. If no callee is matching, the hierarchy is used to find all
	 * possible callees.
	 *
	 * @param stmt   The current statement
	 * @param method The method for which to find implementations
	 * @return A set containing implementations of the given method
	 */
	protected Collection getImplementors(Stmt stmt, SootMethod method) {
		Set implementors = new HashSet();
		if (stmt != null) {
			for (SootMethod callee : manager.getICFG().getCalleesOfCallAt(stmt)) {
				if (!callee.isConcrete())
					continue;

				SootClass gapClass = method.getDeclaringClass();
				SootClass implClass = callee.getDeclaringClass();
				if (fastHierarchy.canStoreClass(implClass, gapClass))
					implementors.add(callee);
			}
		}

		if (!implementors.isEmpty())
			return implementors;

		final String subSig = method.getSubSignature();
		List workList = new ArrayList();
		workList.add(method.getDeclaringClass());
		Set doneSet = new HashSet();

		while (!workList.isEmpty()) {
			SootClass curClass = workList.remove(0);
			if (!doneSet.add(curClass))
				continue;

			if (curClass.isInterface()) {
				workList.addAll(hierarchy.getImplementersOf(curClass));
				workList.addAll(hierarchy.getSubinterfacesOf(curClass));
			} else
				workList.addAll(hierarchy.getSubclassesOf(curClass));

			SootMethod ifm = curClass.getMethodUnsafe(subSig);
			if (ifm != null && ifm.isConcrete())
				implementors.add(ifm);
		}

		return implementors;
	}

	/**
	 * Applies a data flow summary to a given tainted access path
	 * 
	 * @param flow       The data flow summary to apply
	 * @param propagator The access path propagator on which to apply the given flow
	 * @return The access path propagator obtained by applying the given data flow
	 *         summary to the given access path propagator. if the summary is not
	 *         applicable, null is returned.
	 */
	protected AccessPathPropagator applyFlow(MethodFlow flow, AccessPathPropagator propagator) {
		final AbstractFlowSinkSource flowSource = flow.source();
		AbstractFlowSinkSource flowSink = flow.sink();
		final Taint taint = propagator.getTaint();

		// Make sure that the base type of the incoming taint and the one of
		// the summary are compatible
		boolean typesCompatible = flowSource.getBaseType() == null
				|| isCastCompatible(TypeUtils.getTypeFromString(taint.getBaseType()),
						TypeUtils.getTypeFromString(flowSource.getBaseType()));
		if (!typesCompatible)
			return null;

		// If this flow starts at a gap, our current taint must be at that gap
		if (taint.getGap() != flow.source().getGap())
			return null;

		// Maintain the stack of access path propagations
		final AccessPathPropagator parent;
		final GapDefinition gap, taintGap;
		final Stmt stmt;
		final Abstraction d1, d2;
		if (flowSink.getGap() != null) { // ends in gap, push on stack
			parent = propagator;
			gap = flowSink.getGap();
			stmt = null;
			d1 = null;
			d2 = null;
			taintGap = null;
		} else {
			parent = safePopParent(propagator);
			gap = propagator.getParent() == null ? null : propagator.getParent().getGap();
			stmt = propagator.getParent() == null ? propagator.getStmt() : propagator.getParent().getStmt();
			d1 = propagator.getParent() == null ? propagator.getD1() : propagator.getParent().getD1();
			d2 = propagator.getParent() == null ? propagator.getD2() : propagator.getParent().getD2();
			taintGap = propagator.getGap();
		}

		boolean addTaint = flowMatchesTaint(flow, taint, propagator.getStmt());

		// If we didn't find a match, there's little we can do
		if (!addTaint)
			return null;

		// Construct a new propagator
		Taint newTaint = null;
		if (flow.isCustom()) {
			newTaint = addCustomSinkTaint(flow, taint, taintGap);
		} else
			newTaint = addSinkTaint(flow, taint, taintGap, stmt, propagator.isInversePropagator());
		if (newTaint == null)
			return null;

		if (d2 != null && !d2.isAbstractionActive() && !d2.dependsOnCutAP() && flowSink.isReturn()) {
			// Special case: x = f(y), if y is inactive, we can only taint x if we have some
			// fields left in x
			// See ln. 224 of InfoflowProblem.
			if (!newTaint.hasAccessPath() || newTaint.getAccessPathLength() == 0)
				return null;
		}

		AccessPathPropagator newPropagator = new AccessPathPropagator(newTaint, gap, parent, stmt, d1, d2,
				propagator.isInversePropagator());
		return newPropagator;
	}

	/**
	 * Checks whether the type tracked in the access path is compatible with the
	 * type of the base object expected by the flow summary
	 * 
	 * @param baseType  The base type tracked in the access path
	 * @param checkType The type in the summary
	 * @return True if the tracked base type is compatible with the type expected by
	 *         the flow summary, otherwise false
	 */
	protected boolean isCastCompatible(Type baseType, Type checkType) {
		if (baseType == null || checkType == null)
			return false;

		if (baseType == Scene.v().getObjectType())
			return checkType instanceof RefType;
		if (checkType == Scene.v().getObjectType())
			return baseType instanceof RefType;

		return baseType == checkType || fastHierarchy.canStoreType(baseType, checkType)
				|| fastHierarchy.canStoreType(checkType, baseType);
	}

	/**
	 * Gets the parameter index to which the given access path refers
	 * 
	 * @param stmt  The invocation statement
	 * @param curAP The access path
	 * @return The parameter index to which the given access path refers if it
	 *         exists. Otherwise, if the given access path does not refer to a
	 *         parameter, -1 is returned.
	 */
	protected int getParameterIndex(Stmt stmt, AccessPath curAP) {
		if (!stmt.containsInvokeExpr())
			return -1;
		if (curAP.isStaticFieldRef())
			return -1;

		final InvokeExpr iexpr = stmt.getInvokeExpr();
		for (int i = 0; i < iexpr.getArgCount(); i++)
			if (iexpr.getArg(i) == curAP.getPlainValue())
				return i;
		return -1;
	}

	/**
	 * Gets the parameter index to which the given access path refers
	 * 
	 * @param sm    The method in which to check the parameter locals
	 * @param curAP The access path
	 * @return The parameter index to which the given access path refers if it
	 *         exists. Otherwise, if the given access path does not refer to a
	 *         parameter, -1 is returned.
	 */
	protected int getParameterIndex(SootMethod sm, AccessPath curAP) {
		if (curAP.isStaticFieldRef())
			return -1;

		for (int i = 0; i < sm.getParameterCount(); i++)
			if (curAP.getPlainValue() == sm.getActiveBody().getParameterLocal(i))
				return i;
		return -1;
	}

	/**
	 * Checks whether the fields mentioned in the given taint correspond to those of
	 * the given flow source
	 * 
	 * @param taintedPath The tainted access path
	 * @param flowSource  The flow source with which to compare the taint
	 * @return True if the given taint references the same fields as the given flow
	 *         source, otherwise false
	 */
	protected boolean compareFields(Taint taintedPath, AbstractFlowSinkSource flowSource) {
		// if we have x.f....fn and the source is x.f'.f1'...f'n+1 and we don't
		// taint sub, we can't have a match
		if (taintedPath.getAccessPathLength() < flowSource.getAccessPathLength()) {
			if (!taintedPath.taintSubFields() || flowSource.isMatchStrict())
				return false;
		}

		// Compare the shared sub-path
		for (int i = 0; i < taintedPath.getAccessPathLength() && i < flowSource.getAccessPathLength(); i++) {
			String taintField = taintedPath.getAccessPath().getField(i);
			String sourceField = flowSource.getAccessPath().getField(i);
			if (sourceField.equals(taintField))
				continue;

			SootField taintSootField = safeGetField(taintField);
			SootField sourceSootField = safeGetField(sourceField);
			if (taintSootField.equals(sourceSootField))
				continue;

			Scene sc = Scene.v();
			SootClass sourceClass = sc.getSootClassUnsafe(Scene.signatureToClass(sourceField));
			SootClass taintClass = sc.getSootClassUnsafe(Scene.signatureToClass(taintField));
			if (sourceClass == null || taintClass == null)
				return false;
			if (sc.getOrMakeFastHierarchy().canStoreClass(taintClass, sourceClass)) {
				if (!Scene.signatureToSubsignature(sourceField).equals(Scene.signatureToSubsignature(taintField)))
					return false;
			} else
				return false;
		}

		return true;
	}

	/**
	 * Gets the field with the specified signature if it exists, otherwise returns
	 * null
	 * 
	 * @param fieldSig The signature of the field to retrieve
	 * @return The field with the given signature if it exists, otherwise null
	 */
	protected SootField safeGetField(String fieldSig) {
		if (fieldSig == null || fieldSig.isEmpty())
			return null;

		SootField sf = Scene.v().grabField(fieldSig);
		if (sf != null)
			return sf;

		// This field does not exist, so we need to create it
		int colonIdx = fieldSig.indexOf(":");
		String className = fieldSig.substring(1, colonIdx);
		SootClass sc = Scene.v().getSootClassUnsafe(className, true);
		if (sc.resolvingLevel() < SootClass.SIGNATURES && !sc.isPhantom()) {
			System.err.println("WARNING: Class not loaded: " + sc);
			return null;
		}

		String typeAndName = fieldSig.substring(colonIdx + 2, fieldSig.length() - 1);
		int spaceIdx = typeAndName.indexOf(" ");
		String type = typeAndName.substring(0, spaceIdx);
		String fieldName = typeAndName.substring(spaceIdx + 1);

		SootFieldRef ref = Scene.v().makeFieldRef(sc, fieldName, TypeUtils.getTypeFromString(type), false);
		return ref.resolve();
	}

	/**
	 * Gets an array of fields with the specified signatures
	 * 
	 * @param accessPath The access path from which to retrieve the list of the
	 *                   field signatures
	 * @return The Array of fields with the given signature if all exists, otherwise
	 *         null
	 */
	protected SootField[] safeGetFields(AccessPathFragment accessPath) {
		if (accessPath == null || accessPath.isEmpty())
			return null;
		return safeGetFields(accessPath.getFields());
	}

	/**
	 * Gets an array of fields with the specified signatures
	 * 
	 * @param fieldSigs , list of the field signatures to retrieve
	 * @return The Array of fields with the given signature if all exists, otherwise
	 *         null
	 */
	protected SootField[] safeGetFields(String[] fieldSigs) {
		if (fieldSigs == null || fieldSigs.length == 0)
			return null;
		SootField[] fields = new SootField[fieldSigs.length];
		for (int i = 0; i < fieldSigs.length; i++) {
			fields[i] = safeGetField(fieldSigs[i]);
			if (fields[i] == null)
				return null;
		}
		return fields;
	}

	/**
	 * Gets an array of types with the specified class names
	 * 
	 * @param accessPath The access path from which to retrieve the field types
	 * @param fields     The fields from which to get the types if we don't have any
	 *                   explicit ones
	 * @return The Array of fields with the given signature if all exists, otherwise
	 *         null
	 */
	protected Type[] safeGetTypes(AccessPathFragment accessPath, SootField[] fields) {
		if (accessPath == null || accessPath.isEmpty())
			return null;
		return safeGetTypes(accessPath.getFieldTypes(), fields);
	}

	/**
	 * Gets an array of types with the specified class names
	 * 
	 * @param fieldTypes , list of the type names to retrieve
	 * @param fields     The fields from which to get the types if we don't have any
	 *                   explicit ones
	 * @return The Array of fields with the given signature if all exists, otherwise
	 *         null
	 */
	protected Type[] safeGetTypes(String[] fieldTypes, SootField[] fields) {
		if (fieldTypes == null || fieldTypes.length == 0) {
			// If we don't have type information, but fields, we can use the declared field
			// types
			if (fields != null && fields.length > 0) {
				Type[] types = new Type[fields.length];
				for (int i = 0; i < fields.length; i++)
					types[i] = fields[i].getType();
				return types;
			}
			return null;
		}

		// Parse the explicit type information
		Type[] types = new Type[fieldTypes.length];
		for (int i = 0; i < fieldTypes.length; i++)
			types[i] = TypeUtils.getTypeFromString(fieldTypes[i]);
		return types;
	}

	protected ContainerContext[][] safeGetContexts(AccessPathFragment accessPath) {
		if (accessPath == null || accessPath.isEmpty())
			return null;
		return accessPath.getContexts();
	}

	/**
	 * Given the taint at the source and the flow, computes the taint at the sink.
	 * This method allows custom extensions to the taint wrapper. The default
	 * implementation always returns null.
	 * 
	 * @param flow  The flow between source and sink
	 * @param taint The taint at the source statement
	 * @param gap   The gap at which the new flow will hold
	 * @return The taint at the sink that is obtained when applying the given flow
	 *         to the given source taint
	 */
	protected Taint addCustomSinkTaint(MethodFlow flow, Taint taint, GapDefinition gap) {
		return null;
	}

	/**
	 * Given the taint at the source and the flow, computes the taint at the sink
	 * 
	 * @param flow  The flow between source and sink
	 * @param taint The taint at the source statement
	 * @param gap   The gap at which the new flow will hold
	 * @return The taint at the sink that is obtained when applying the given flow
	 *         to the given source taint
	 */
	protected Taint addSinkTaint(MethodFlow flow, Taint taint, GapDefinition gap, Stmt stmt,
			boolean inversePropagator) {
		final AbstractFlowSinkSource flowSource = flow.source();
		final AbstractFlowSinkSource flowSink = flow.sink();
		final boolean taintSubFields = flow.sink().taintSubFields();
		final Boolean checkTypes = flow.getTypeChecking();

		AccessPathFragment remainingFields = cutSubFields(flow, getRemainingFields(flowSource, taint));
		AccessPathFragment appendedFields = AccessPathFragment.append(flowSink.getAccessPath(), remainingFields);

		int lastCommonAPIdx = Math.min(flowSource.getAccessPathLength(), taint.getAccessPathLength());

		Type sinkType = TypeUtils.getTypeFromString(getAssignmentType(flowSink, flow.methodSig()));
		Type taintType = TypeUtils.getTypeFromString(getAssignmentType(taint, lastCommonAPIdx - 1));

		// For type checking, we need types
		if ((checkTypes == null || checkTypes.booleanValue()) && sinkType != null && taintType != null) {
			// If we taint something in the base object, its type must match. We
			// might have a taint for "a" in o.add(a) and need to check whether
			// "o" matches the expected type in our summary.
			if (!(sinkType instanceof PrimType) && !isCastCompatible(taintType, sinkType)
					&& flowSink.getType() == SourceSinkType.Field) {
				// If the target is an array, the value might also flow into an
				// element
				boolean found = false;
				while (sinkType instanceof ArrayType) {
					sinkType = ((ArrayType) sinkType).getElementType();
					if (isCastCompatible(taintType, sinkType)) {
						found = true;
						break;
					}
				}
				while (taintType instanceof ArrayType) {
					taintType = ((ArrayType) taintType).getElementType();
					if (isCastCompatible(taintType, sinkType)) {
						found = true;
						break;
					}
				}
				if (!found)
					return null;
			}
		}

		// If we enter a gap with a type "GapBaseObject", we need to convert
		// it to a regular field
		SourceSinkType sourceSinkType = flowSink.getType();
		if (flowSink.getType() == SourceSinkType.GapBaseObject && remainingFields != null && !remainingFields.isEmpty())
			sourceSinkType = SourceSinkType.Field;

		String sBaseType = sinkType == null ? null : "" + sinkType;
		if (!flow.getIgnoreTypes()) {
			// Compute the new base type
			Type newBaseType = manager.getTypeUtils().getMorePreciseType(taintType, sinkType);
			if (newBaseType == null)
				newBaseType = sinkType;

			sBaseType = String.valueOf(newBaseType);
			// Set the correct type. In case x -> b.x, the new type is not the type
			// of b, but of the field x.
			if (flowSink.hasAccessPath()) {
				if (appendedFields != null)
					appendedFields = appendedFields.updateFieldType(flowSink.getAccessPathLength() - 1, sBaseType);
				sBaseType = flowSink.getBaseType();
			}
		}

		ContainerContext[] baseCtxt = null;
		if (containerStrategy != null) {
			if (flow.sink().isConstrained()) {
				ContainerContext[] ctxt = concretizeFlowConstraints(flow.getConstraints(), stmt,
						taint.hasAccessPath() ? taint.getAccessPath().getFirstFieldContext() : null);
				if (appendedFields != null && ctxt != null && !containerStrategy.shouldSmash(ctxt))
					appendedFields = appendedFields.addContext(ctxt);
			} else if (flow.sink().append() && !appendInfiniteAscendingChain(flow, stmt)) {
				ContainerContext[] stmtCtxt = concretizeFlowConstraints(flow.getConstraints(), stmt, null);
				ContainerContext[] taintCtxt = taint.getAccessPath().getFirstFieldContext();
				ContainerContext[] ctxt = containerStrategy.append(stmtCtxt, taintCtxt);
				if (ctxt != null && !containerStrategy.shouldSmash(ctxt) && appendedFields != null)
					appendedFields = appendedFields.addContext(ctxt);
			} else if (flow.sink().shiftLeft() && !inversePropagator) {
				ContainerContext[] taintCtxt = taint.getAccessPath().getFirstFieldContext();
				if (taintCtxt != null) {
					Tristate lte = flowShiftLeft(flowSource, flow, taint, stmt);
					if (!lte.isFalse()) {
						ContainerContext newCtxt = containerStrategy.shift(taintCtxt[0], -1, lte.isTrue());
						if (newCtxt != null && appendedFields != null) {
							appendedFields = appendedFields.addContext(
									newCtxt.containsInformation() ? new ContainerContext[] { newCtxt } : null);
						}
					}
				}
			} else if (flow.sink().shiftRight() && !inversePropagator) {
				ContainerContext[] taintCtxt = taint.getAccessPath().getFirstFieldContext();
				if (taintCtxt != null) {
					Tristate lte = flowShiftRight(flowSource, flow, taint, stmt);
					if (!lte.isFalse()) {
						ContainerContext newCtxt = containerStrategy.shift(taintCtxt[0], 1, lte.isTrue());
						if (newCtxt != null && appendedFields != null) {
							appendedFields = appendedFields.addContext(
									newCtxt.containsInformation() ? new ContainerContext[] { newCtxt } : null);
						}
					}
				}
			} else if (flow.sink().keepConstraint() || (flow.sink().keepOnRO() && containerStrategy.isReadOnly(stmt))) {
				ContainerContext[] ctxt;
				if (lastCommonAPIdx == 0)
					ctxt = taint.getBaseContext();
				else
					ctxt = taint.getAccessPath().getContext(lastCommonAPIdx - 1);

				if (ctxt != null) {
					// We may only address one constraint in the source and have another constraint
					// that is kept in the sink. Here we filter the used constraints out.
					ctxt = filterContexts(ctxt, flow.getConstraints());
					if (appendedFields == null || appendedFields.isEmpty())
						baseCtxt = ctxt;
					else
						appendedFields = appendedFields.addContext(ctxt);
				}
			}
		}

		// Taint the correct fields
		return new Taint(sourceSinkType, flowSink.getParameterIndex(), sBaseType, baseCtxt, appendedFields,
				taintSubFields || taint.taintSubFields(), gap);
	}

	/**
	 * Adding a list with addAll to itself might create an infinite ascending
	 * chain...
	 *
	 * @param flow method flow
	 * @param stmt current statement
	 * @return true if there might be an infinite ascending chain
	 */
	private boolean appendInfiniteAscendingChain(MethodFlow flow, Stmt stmt) {
		PointsToAnalysis pta = Scene.v().getPointsToAnalysis();
		AccessPath sourceAp = createAccessPathFromTaint(new Taint(flow.source().getType(),
				flow.source().getParameterIndex(), flow.source().getBaseType(), true), stmt, false);
		AccessPath sinkAp = createAccessPathFromTaint(
				new Taint(flow.sink().getType(), flow.sink().getParameterIndex(), flow.sink().getBaseType(), true),
				stmt, false);
		PointsToSet sourcePts = pta.reachingObjects(sourceAp.getPlainValue());
		PointsToSet sinkPts = pta.reachingObjects(sinkAp.getPlainValue());
		return sourcePts.hasNonEmptyIntersection(sinkPts);
	}

	private ContainerContext[] filterContexts(ContainerContext[] ctxt, FlowConstraint[] constraints) {
		// If the current method does not use constraints,
		// we keep the full context.
		if (constraints.length == 0)
			return ctxt;

		ContainerContext[] newCtxt = new ContainerContext[ctxt.length];
		int i = 0;
		for (int k = 0; k < constraints.length; k++) {
			FlowConstraint constraint = constraints[k];
			if (constraint.getType() == SourceSinkType.Any) {
				newCtxt[i++] = ctxt[k];
			}
		}
		if (i == 0)
			return null;
		else if (i == ctxt.length)
			return newCtxt;
		return Arrays.copyOf(newCtxt, i);
	}

	/**
	 * Cuts the given access path if required for the given method flow. This method
	 * can cut the incoming access path to shorter lengths or abandon the original
	 * access path altogether.
	 * 
	 * @param flow       The method flow specification
	 * @param accessPath The remaining fields from the original access path
	 * @return The fields that shall be appended to the new access path. This is
	 *         usually a prefix of the original remaining fields
	 */
	protected AccessPathFragment cutSubFields(MethodFlow flow, AccessPathFragment accessPath) {
		if (isCutSubFields(flow))
			return null;
		else
			return accessPath;
	}

	/**
	 * Checks whether the following fields shall be deleted when applying the given
	 * flow specification
	 * 
	 * @param flow The flow specification to check
	 * @return true if the following fields shall be deleted, false otherwise
	 */
	protected boolean isCutSubFields(MethodFlow flow) {
		Boolean cut = flow.getCutSubFields();
		return cut != null && cut.booleanValue();
	}

	/**
	 * Gets the type at the given position from a taint.
	 * 
	 * @param taint The taint from which to get the propagation type
	 * @param idx   The index inside the access path from which to get the type. -1
	 *              refers to the base type
	 * @return The type at the given index inside the access path
	 */
	protected String getAssignmentType(Taint taint, int idx) {
		if (idx < 0)
			return taint.getBaseType();

		final AccessPathFragment accessPath = taint.getAccessPath();
		if (accessPath == null)
			return null;
		final String[] fieldTypes = accessPath.getFieldTypes();

		return fieldTypes == null ? null : fieldTypes[idx];
	}

	/**
	 * Gets the type that is finally assigned when propagating this source or sink.
	 * For an access path a.b.c, this would be the type of "c".
	 * 
	 * @param srcSink   The source or sink from which to get the propagation type
	 * @param methodSig The method subsignature for retrieving missing types from
	 *                  the signature
	 * @return The type of the value which the access path of the given source or
	 *         sink finally references
	 */
	protected String getAssignmentType(AbstractFlowSinkSource srcSink, String methodSig) {
		if (!srcSink.hasAccessPath()) {
			String baseType = srcSink.getBaseType();
			if (baseType != null)
				return baseType;

			// If we have no base type in the summary, we parse it from the method signature
			if (srcSink.hasGap()) {
				// We take the signature of the gap
				String gapSig = srcSink.getGap().getSignature();
				SootMethodAndClass smac = SootMethodRepresentationParser.v().parseSootMethodString(gapSig);
				if (srcSink.isReturn())
					return smac.getReturnType();
				else if (srcSink.isParameter())
					return smac.getParameters().get(srcSink.getParameterIndex());
				else if (srcSink.isGapBaseObject())
					return smac.getClassName();
			} else {
				// We take the signature of the method for which we have the summary
				MethodSubSignature subsig = new MethodSubSignature(Scene.v().getSubSigNumberer().findOrAdd(methodSig));
				if (srcSink.isReturn())
					return subsig.returnType.toString();
				else if (srcSink.isParameter())
					return subsig.parameterTypes.get(srcSink.getParameterIndex()).toString();
			}
			return null;
		}

		// If we don't have explicit access path types, we use the declared
		// types instead
		final AccessPathFragment accessPath = srcSink.getAccessPath();
		if (accessPath.getFieldTypes() == null && accessPath.getFields() != null) {
			String[] ap = accessPath.getFields();
			String apElement = ap[srcSink.getAccessPathLength() - 1];

			Pattern pattern = Pattern.compile("^\\s*<(.*?):\\s*(.*?)>\\s*$");
			Matcher matcher = pattern.matcher(apElement);
			if (matcher.find()) {
				return matcher.group(1);
			}
		}

		return accessPath.getFieldTypes() == null ? null
				: accessPath.getFieldTypes()[srcSink.getAccessPathLength() - 1];
	}

	/**
	 * Gets the remaining fields which are tainted, but not covered by the given
	 * flow summary source
	 * 
	 * @param flowSource  The flow summary source
	 * @param taintedPath The tainted access path
	 * @return The remaining fields which are tainted in the given access path, but
	 *         which are not covered by the given flow summary source
	 */
	protected AccessPathFragment getRemainingFields(AbstractFlowSinkSource flowSource, Taint taintedPath) {
		if (!flowSource.hasAccessPath())
			return taintedPath.getAccessPath();

		int fieldCnt = taintedPath.getAccessPathLength() - flowSource.getAccessPathLength();
		if (fieldCnt <= 0)
			return null;

		final AccessPathFragment taintedAP = taintedPath.getAccessPath();
		String[] oldFields = taintedAP.getFields();
		String[] oldFieldTypes = taintedAP.getFieldTypes();

		String[] fields = new String[fieldCnt];
		String[] fieldTypes = new String[fieldCnt];
		System.arraycopy(oldFields, flowSource.getAccessPathLength(), fields, 0, fieldCnt);
		System.arraycopy(oldFieldTypes, flowSource.getAccessPathLength(), fieldTypes, 0, fieldCnt);

		ContainerContext[][] contexts = new ContainerContext[fieldCnt][];
		System.arraycopy(taintedAP.getContexts(), flowSource.getAccessPathLength(), contexts, 0, fieldCnt);
		return new AccessPathFragment(fields, fieldTypes, contexts);
	}

	/**
	 * Gets the base object on which the method is invoked
	 * 
	 * @param stmt The statement for which to get the base of the method call
	 * @return The base object of the method call if it exists, otherwise null
	 */
	private Value getMethodBase(Stmt stmt) {
		if (!stmt.containsInvokeExpr())
			throw new RuntimeException("Statement is not a method call: " + stmt);
		InvokeExpr invExpr = stmt.getInvokeExpr();
		if (invExpr instanceof InstanceInvokeExpr)
			return ((InstanceInvokeExpr) invExpr).getBase();
		return null;
	}

	@Override
	public boolean isExclusive(Stmt stmt, Abstraction taintedPath) {
		// If we directly support the callee, we are exclusive in any case
		if (supportsCallee(stmt)) {
			wrapperHits.getAndIncrement();
			return true;
		}

		// If the fallback wrapper supports the method and is exclusive for it,
		// we are as well
		if (fallbackWrapper != null && fallbackWrapper.isExclusive(stmt, taintedPath)) {
			wrapperHits.getAndIncrement();
			return true;
		}

		// We may also be exclusive for a complete class
		if (stmt.containsInvokeExpr()) {
			final InvokeExpr invExpr = stmt.getInvokeExpr();
			SootClass targetClass = invExpr.getMethod().getDeclaringClass();
			// The target class should never be null, but it happened
			if (targetClass != null) {
				// Are the class flows configured to be exclusive?
				final String targetClassName = targetClass.getName();
				ClassMethodSummaries cms = flows.getClassFlows(targetClassName);
				if (cms != null && cms.isExclusiveForClass()) {
					wrapperHits.getAndIncrement();
					return true;
				}

				// Check for classes excluded by meta data
				ClassSummaries summaries = flows.getSummaries();
				SummaryMetaData metaData = summaries.getMetaData();
				if (metaData != null) {
					if (metaData.isClassExclusive(targetClassName)) {
						wrapperHits.getAndIncrement();
						return true;
					}
				}

				SummaryResponse resp = summaryResolver.resolve(SummaryQuery.fromStmt(stmt));
				if (resp.isClassSupported())
					return true;
			}
		}
		wrapperMisses.getAndIncrement();
		return false;
	}

	@Override
	public boolean supportsCallee(SootMethod method) {
		// Check whether we directly support that class
		SootClass declClass = method.getDeclaringClass();
		if (declClass == null)
			return false;

		ClassMethodSummaries cms = flows.getClassFlows(declClass.getName());
		if (cms == null)
			return false;

		// We assume for exclusive classes that we have all summaries for that class.
		if (cms.isExclusiveForClass())
			return true;

		// Otherwise, we check whether we have a summary for the method.
		MethodSummaries summaries = cms.getMethodSummaries().filterForMethod(method.getSubSignature());
		return summaries != null && !summaries.isEmpty();
	}

	@Override
	public boolean supportsCallee(Stmt callSite) {
		if (!callSite.containsInvokeExpr())
			return false;

		if (manager == null) {
			// If we support the method, we are exclusive for it
			SootMethod method = callSite.getInvokeExpr().getMethod();
			if (supportsCallee(method))
				return true;
		} else {
			// Check if we are exclusive for any potential callee
			for (SootMethod callee : manager.getICFG().getCalleesOfCallAt(callSite))
				if (!callee.isStaticInitializer())
					if (supportsCallee(callee))
						return true;
		}

		return false;
	}

	/**
	 * Gets the propagators that have been registered as being passed into user code
	 * with the given context and for the given callee
	 * 
	 * @param abs    The context abstraction with which the taint was passed into
	 *               the callee
	 * @param callee The callee into which the taint was passed
	 * @return The of taint propagators passed into the given callee with the given
	 *         context. If no such propagators exist, null is returned.
	 */
	Set getUserCodeTaints(Abstraction abs, SootMethod callee) {
		return this.userCodeTaints.get(new Pair<>(abs, callee));
	}

	@Override
	public int getWrapperHits() {
		return wrapperHits.get();
	}

	@Override
	public int getWrapperMisses() {
		return wrapperMisses.get();
	}

	@Override
	public Set getAliasesForMethod(Stmt stmt, Abstraction d1, Abstraction taintedAbs) {
		// We only care about method invocations
		if (!stmt.containsInvokeExpr())
			return Collections.singleton(taintedAbs);

		// Get the cached data flows
		final SootMethod method = stmt.getInvokeExpr().getMethod();
		ClassSummaries flowsInCallees = getFlowSummariesForMethod(stmt, method, taintedAbs, null);

		// If we have no data flows, we can abort early
		if (flowsInCallees == null || flowsInCallees.isEmpty()) {
			if (fallbackWrapper == null)
				return null;
			else
				return fallbackWrapper.getAliasesForMethod(stmt, d1, taintedAbs);
		}

		// Create a level-0 propagator for the initially tainted access path
		Set taintsFromAP = createTaintFromAccessPathOnCall(taintedAbs.getAccessPath(), stmt, true, null);
		if (taintsFromAP == null || taintsFromAP.isEmpty())
			return Collections.emptySet();

		ByReferenceBoolean killIncomingTaint = new ByReferenceBoolean();
		Set res = null;
		for (String className : flowsInCallees.getClasses()) {
			boolean reverseFlows = manager.getConfig()
					.getDataFlowDirection() == InfoflowConfiguration.DataFlowDirection.Backwards;

			// Get the flows in this class
			ClassMethodSummaries classFlows = flowsInCallees.getClassSummaries(className);
			if (classFlows == null)
				continue;

			// Get the method-level flows
			MethodSummaries flowsInCallee = classFlows.getMethodSummaries();
			if (flowsInCallee == null || flowsInCallee.isEmpty())
				continue;
			flowsInCallee = flowsInCallee.filterForAliases();
			if (flowsInCallee == null || flowsInCallee.isEmpty())
				continue;

			boolean killTaint = false;
			List workList = new ArrayList();
			for (Taint taint : taintsFromAP) {
				boolean preventPropagation = false;
				if (flowsInCallee.hasClears()) {
					for (MethodClear clear : flowsInCallee.getAllClears()) {
						if (clear.isAlias(taint) && clearMatchesTaint(clear, taint, stmt)) {
							killTaint = true;
							preventPropagation = clear.preventPropagation();
							break;
						}
					}
				}

				if (killTaint)
					killIncomingTaint.value = true;
				if (!preventPropagation)
					workList.add(new AccessPathPropagator(taint, null, null, stmt, d1, taintedAbs, !reverseFlows));
			}

			// Apply the data flows until we reach a fixed point
			Set resCallee = applyFlowsIterative(flowsInCallee, workList, false, stmt, taintedAbs, false);
			if (resCallee != null && !resCallee.isEmpty()) {
				if (res == null)
					res = new HashSet<>();
				res.addAll(resCallee);
			}
		}

		// We always retain the incoming taint
		if (res == null || res.isEmpty())
			return killIncomingTaint.value ? null : Collections.singleton(taintedAbs);

		// Create abstractions from the access paths
		Set resAbs = new HashSet<>(res.size() + 1);
		if (!killIncomingTaint.value)
			resAbs.add(taintedAbs);
		for (AccessPath ap : res) {
			Abstraction newAbs = taintedAbs.deriveNewAbstraction(ap, stmt);
			newAbs.setCorrespondingCallSite(stmt);
			resAbs.add(newAbs);
		}
		return resAbs;
	}

	/**
	 * Sets whether missing summaries for classes shall be reported on the
	 * command-line
	 * 
	 * @param report True if missing summaries shall be reported on the command
	 *               line, otherwise false
	 */
	public void setReportMissingDummaries(boolean report) {
		this.reportMissingSummaries = report;
	}

	/**
	 * Sets the fallback taint wrapper to be used if there is no StubDroid summary
	 * for a certain class
	 * 
	 * @param fallbackWrapper The fallback taint wrapper to be used if there is no
	 *                        StubDroid summary for a certain class
	 */
	public void setFallbackTaintWrapper(ITaintPropagationWrapper fallbackWrapper) {
		this.fallbackWrapper = fallbackWrapper;
	}

	/**
	 * Gets the provider from which this taint wrapper loads it flows
	 * 
	 * @return The provider from which this taint wrapper loads it flows
	 */
	public IMethodSummaryProvider getProvider() {
		return this.flows;
	}

	@Override
	public Set getInverseTaintsForMethod(Stmt stmt, Abstraction d1, Abstraction taintedAbs) {
		// We only care about method invocations
		if (!stmt.containsInvokeExpr())
			return Collections.singleton(taintedAbs);

		ByReferenceBoolean classSupported = new ByReferenceBoolean(false);
		// Get the cached data flows
		final SootMethod method = stmt.getInvokeExpr().getMethod();
		ClassSummaries flowsInCallees = getFlowSummariesForMethod(stmt, method, taintedAbs, classSupported);

		// If we have no data flows, we can abort early
		if (flowsInCallees.isEmpty()) {
			if (classSupported.value)
				return Collections.singleton(taintedAbs);

			if (fallbackWrapper != null && fallbackWrapper instanceof IReversibleTaintWrapper)
				return ((IReversibleTaintWrapper) fallbackWrapper).getInverseTaintsForMethod(stmt, d1, taintedAbs);
			else
				return null;
		}

		// Create a level-0 propagator for the initially tainted access path
		ByReferenceBoolean killIncomingTaint = new ByReferenceBoolean();
		Set taintsFromAP = createTaintFromAccessPathOnCall(taintedAbs.getAccessPath(), stmt, true,
				killIncomingTaint);
		if (taintsFromAP == null || taintsFromAP.isEmpty())
			return Collections.emptySet();

		Set res = null;
		for (String className : flowsInCallees.getClasses()) {
			// Get the flows in this class
			ClassMethodSummaries classFlows = flowsInCallees.getClassSummaries(className);
			if (classFlows == null)
				continue;

			// Get the method-level flows
			MethodSummaries flowsInCallee = classFlows.getMethodSummaries();
			if (flowsInCallee == null || flowsInCallee.isEmpty())
				continue;

			List workList = new ArrayList();
			for (Taint taint : taintsFromAP) {
				if (!killIncomingTaint.value && flowsInCallee.hasClears()) {
					for (MethodClear clear : flowsInCallee.getAllClears()) {
						if (clearMatchesTaint(clear, taint, stmt)) {
							killIncomingTaint.value = true;
							break;
						}
					}
				}

				workList.add(new AccessPathPropagator(taint, null, null, stmt, d1, taintedAbs, true));
			}

			// Apply the data flows until we reach a fixed point
			Set resCallee = applyFlowsIterative(flowsInCallee, workList, true, stmt, taintedAbs, false);
			if (resCallee != null && !resCallee.isEmpty()) {
				if (res == null)
					res = new HashSet<>();
				res.addAll(resCallee);
			}
		}

		// We always retain the incoming taint
		if (res == null || res.isEmpty()) {
			// except it is overwritten
			if (killIncomingTaint.value)
				return null;
			return Collections.singleton(taintedAbs);
		}

		// Create abstractions from the access paths
		Set resAbs = new HashSet<>(res.size() + 1);
		if (!killIncomingTaint.value)
			resAbs.add(taintedAbs);
		for (AccessPath ap : res) {
			Abstraction newAbs = taintedAbs.deriveNewAbstraction(ap, stmt);
			newAbs.setCorrespondingCallSite(stmt);
			resAbs.add(newAbs);
		}
		return resAbs;
	}

	@Override
	public Set getTaintsForMethodApprox(Stmt stmt, Abstraction d1, Abstraction source) {
		if (containerStrategy == null)
			return null;

		if (source.getAccessPath().getFragmentCount() == 0
				|| Arrays.stream(source.getAccessPath().getFragments()).noneMatch(f -> f.hasContext()))
			return null;

		if (!(stmt.getInvokeExpr() instanceof InstanceInvokeExpr))
			return null;
		InstanceInvokeExpr iiExpr = (InstanceInvokeExpr) stmt.getInvokeExpr();
		Local base = (Local) iiExpr.getBase();

		// We also need to shift if the base and the taint MAY alias. But we need the
		// answer to that now, which clashes with the on-demand alias resolving of
		// FlowDroid. Especially because we are not really able to correlate that a flow
		// originated from an alias query here. So we use some coarser approximations to
		// find out whether we need to shift or not.

		// First, we check whether they must alias, which allows us to strong update
		// here
		Tristate found = Tristate.FALSE();
		boolean mustAlias = !source.getAccessPath().isStaticFieldRef()
				&& manager.getAliasing().mustAlias(source.getAccessPath().getPlainValue(), base, stmt);
		if (mustAlias) {
			// Must alias means we definitely know how to precisely update here
			found = Tristate.TRUE();
		} else {
			// Otherwise use the points-to information of SPARK to approximate here
			PointsToSet basePts = Scene.v().getPointsToAnalysis().reachingObjects(base);
			PointsToSet incomingPts = Scene.v().getPointsToAnalysis()
					.reachingObjects(source.getAccessPath().getPlainValue());

			if (basePts.hasNonEmptyIntersection(incomingPts)) {
				found = Tristate.MAYBE();
			} else if (source.getAccessPath().getFragmentCount() > 0) {
				for (soot.jimple.infoflow.data.AccessPathFragment f : source.getAccessPath().getFragments()) {
					incomingPts = Scene.v().getPointsToAnalysis().reachingObjects(incomingPts, f.getField());
					if (basePts.hasNonEmptyIntersection(incomingPts)) {
						// Both might alias
						found = Tristate.MAYBE();
						break;
					}
				}
			}
		}

		if (found.isFalse())
			return null;

		final SootMethod method = stmt.getInvokeExpr().getMethod();
		final ByReferenceBoolean classSupported = new ByReferenceBoolean();
		ClassSummaries flowsInCallees = getFlowSummariesForMethod(stmt, method, source, classSupported);
		if (flowsInCallees == null || flowsInCallees.isEmpty())
			return null;

		Set res = new NonNullHashSet<>();
		for (String className : flowsInCallees.getClasses()) {
			// Get the flows in this class
			ClassMethodSummaries classFlows = flowsInCallees.getClassSummaries(className);
			if (classFlows == null || classFlows.isEmpty())
				continue;

			// Get the method-level flows
			MethodSummaries flowsInCallee = classFlows.getMethodSummaries().getApproximateFlows();
			if (flowsInCallee == null || !flowsInCallee.hasFlows())
				continue;

			boolean shiftR = false;
			boolean shiftL = false;
			for (MethodFlow flow : flowsInCallee.getAllFlows()) {
				if (flow.sink().getConstraintType() == ConstraintType.SHIFT_RIGHT) {
					shiftR = true;
					break;
				} else if (flow.sink().getConstraintType() == ConstraintType.SHIFT_LEFT) {
					shiftL = true;
					break;
				}
			}

			if (!shiftL && !shiftR) {
				res.add(source);
			} else if (shiftR) {
				soot.jimple.infoflow.data.AccessPathFragment[] fragments = new soot.jimple.infoflow.data.AccessPathFragment[source
						.getAccessPath().getFragmentCount()];
				for (int k = 0; k < fragments.length; k++) {
					soot.jimple.infoflow.data.AccessPathFragment f = source.getAccessPath().getFragments()[k];
					if (f.getContext() == null) {
						fragments[k] = f;
						continue;
					}

					ContainerContext[] ctxt = new ContainerContext[f.getContext().length];
					for (int i = 0; i < ctxt.length; i++) {
						ContainerContext c = f.getContext()[i];
						if (c == null || !c.containsInformation())
							continue;
						ctxt[i] = containerStrategy.shift(c, 1, found.isTrue());
						fragments[k] = f.copyWithNewContext(containerStrategy.shouldSmash(ctxt) ? null : ctxt);
					}
				}
				AccessPath ap = manager.getAccessPathFactory().createAccessPath(source.getAccessPath().getPlainValue(),
						source.getAccessPath().getBaseType(), fragments, source.getAccessPath().getTaintSubFields(),
						false, true, source.getAccessPath().getArrayTaintType());
				res.add(source.deriveNewAbstraction(ap, stmt));
			} else if (shiftL) {
				soot.jimple.infoflow.data.AccessPathFragment[] fragments = new soot.jimple.infoflow.data.AccessPathFragment[source
						.getAccessPath().getFragmentCount()];
				for (int k = 0; k < fragments.length; k++) {
					soot.jimple.infoflow.data.AccessPathFragment f = source.getAccessPath().getFragments()[k];
					if (f.getContext() == null) {
						fragments[k] = f;
						continue;
					}

					ContainerContext[] ctxt = new ContainerContext[f.getContext().length];
					for (int i = 0; i < ctxt.length; i++) {
						ContainerContext c = f.getContext()[i];
						if (c == null || !c.containsInformation())
							continue;
						ctxt[i] = containerStrategy.shift(c, -1, found.isTrue());
					}
					fragments[k] = f.copyWithNewContext(containerStrategy.shouldSmash(ctxt) ? null : ctxt);
				}
				AccessPath ap = manager.getAccessPathFactory().createAccessPath(source.getAccessPath().getPlainValue(),
						source.getAccessPath().getBaseType(), fragments, source.getAccessPath().getTaintSubFields(),
						false, true, source.getAccessPath().getArrayTaintType());
				res.add(source.deriveNewAbstraction(ap, stmt));
			}
		}

		return res.isEmpty() ? Collections.singleton(source) : res;
	}

	protected ContainerContext[] concretizeFlowConstraints(FlowConstraint[] constraints, Stmt stmt,
			ContainerContext[] taintCtxt) {
		if (containerStrategy == null)
			return taintCtxt;
		if (stmt == null)
			System.out.println("x");
		assert stmt.containsInvokeExpr();
		InvokeExpr ie = stmt.getInvokeExpr();
		ContainerContext[] ctxt = new ContainerContext[constraints.length];
		if (constraints.length == 0)
			// e.g. if you call map.toString(), we do not have a constraint
			return taintCtxt;
		for (int i = 0; i < constraints.length; i++) {
			FlowConstraint c = constraints[i];
			switch (c.getType()) {
			case Parameter:
				if (c.isIndexBased())
					ctxt[i] = containerStrategy.getIndexContext(ie.getArg(c.getParamIdx()), stmt);
				else
					ctxt[i] = containerStrategy.getKeyContext(ie.getArg(c.getParamIdx()), stmt);
				break;
			case Implicit:
				assert c.isIndexBased();
				assert ie instanceof InstanceInvokeExpr;
				switch (c.getImplicitLocation()) {
				case First:
					ctxt[i] = containerStrategy.getFirstPosition(((InstanceInvokeExpr) ie).getBase(), stmt);
					break;
				case Last:
					ctxt[i] = containerStrategy.getLastPosition(((InstanceInvokeExpr) ie).getBase(), stmt);
					break;
				case Next:
					ctxt[i] = containerStrategy.getNextPosition(((InstanceInvokeExpr) ie).getBase(), stmt);
					break;
				default:
					throw new RuntimeException("Missing case!");
				}
				break;
			case Any:
				ctxt[i] = UnknownContext.v();
				break;
			default:
				throw new RuntimeException("Unknown context!");
			}
		}

		return ctxt;
	}

	protected Tristate matchesConstraints(final AbstractFlowSinkSource flowSource, final AbstractMethodSummary flow,
			final Taint taint, final Stmt stmt) {
		// If no constrains apply to the flow source, we can unconditionally use it
		if (!flowSource.isConstrained())
			return Tristate.TRUE();
		ContainerContext[] taintContext = taint.getAccessPath().getFirstFieldContext();
		if (taintContext == null)
			return Tristate.TRUE();

		ContainerContext[] stmtCtxt = concretizeFlowConstraints(flow.getConstraints(), stmt, taintContext);
		assert stmtCtxt.length == taintContext.length;
		Tristate state = Tristate.TRUE();
		for (int i = 0; i < stmtCtxt.length; i++) {
			state = state.and(containerStrategy.intersect(taintContext[i], stmtCtxt[i]));
		}

		return flowSource.getConstraintType() == ConstraintType.NO_MATCH ? Tristate.fromBoolean(state.isFalse())
				: state;
	}

	protected Tristate matchesConstraintsOnClear(final AbstractFlowSinkSource flowSource,
			final AbstractMethodSummary flow, final Taint taint, final Stmt stmt) {
		// On clears, we need to under-approximate. If the clear is only valid when a
		// constraint matches and we don't have support on constraints, we refrain from
		// clearing the taint.
		if (flowSource.isConstrained() && containerStrategy == null)
			return Tristate.FALSE();
		return matchesConstraints(flowSource, flow, taint, stmt);
	}

	protected Tristate matchShiftLeft(final AbstractFlowSinkSource flowSource, final AbstractMethodSummary flow,
			final Taint taint, final Stmt stmt) {
		ContainerContext[] taintContext = taint.getAccessPath().getFirstFieldContext();
		if (taintContext == null)
			return Tristate.FALSE();

		ContainerContext[] stmtCtxt = concretizeFlowConstraints(flow.getConstraints(), stmt, taintContext);
		assert stmtCtxt.length == taintContext.length;
		Tristate state = Tristate.TRUE();
		for (int i = 0; i < stmtCtxt.length; i++) {
			state = state.and(containerStrategy.lessThanEqual(taintContext[i], stmtCtxt[i]).negate());
		}

		return state;
	}

	protected Tristate matchShiftRight(final AbstractFlowSinkSource flowSource, final AbstractMethodSummary flow,
			final Taint taint, final Stmt stmt) {
		// If no constrains apply to the flow source, we can unconditionally use it
		ContainerContext[] taintContext = taint.getAccessPath().getFirstFieldContext();
		if (taintContext == null)
			return Tristate.FALSE();

		ContainerContext[] stmtCtxt = concretizeFlowConstraints(flow.getConstraints(), stmt, taintContext);
		assert stmtCtxt.length == taintContext.length;
		Tristate state = Tristate.TRUE();
		for (int i = 0; i < stmtCtxt.length; i++) {
			state = state.and(containerStrategy.lessThanEqual(stmtCtxt[i], taintContext[i]));
		}

		return state;
	}

	/**
	 * Checks whether the given flow summary the given taint
	 *
	 * @param flow  The flow summary to match
	 * @param taint The taint to match
	 * @param stmt  The statement at which to perform the mapping
	 * @return True if the given flow summary matches the given taint, otherwise
	 *         false
	 */
	protected boolean flowMatchesTaint(final MethodFlow flow, final Taint taint, final Stmt stmt) {
		return !flowMatchesTaintInternal(flow.source(), flow, taint, stmt, this::matchesConstraints).isFalse();
	}

	/**
	 * Checks whether the given clear summary the given taint
	 *
	 * @param clear The clear summary to match
	 * @param taint The taint to match
	 * @param stmt  The statement at which to perform the mapping
	 * @return True if the given clear summary matches the given taint, otherwise
	 *         false
	 */
	protected boolean clearMatchesTaint(final MethodClear clear, final Taint taint, final Stmt stmt) {
		return flowMatchesTaintInternal(clear.getClearDefinition(), clear, taint, stmt, this::matchesConstraintsOnClear)
				.isTrue();
	}

	@FunctionalInterface
	protected interface MatchFunction {
		Tristate match(final AbstractFlowSinkSource flowSource, final AbstractMethodSummary flow, final Taint taint,
				final Stmt stmt);
	}

	protected Tristate flowShiftLeft(final AbstractFlowSinkSource flowSource, final AbstractMethodSummary flow,
			final Taint taint, final Stmt stmt) {
		return flowMatchesTaintInternal(flowSource, flow, taint, stmt, this::matchShiftLeft);
	}

	protected Tristate flowShiftRight(final AbstractFlowSinkSource flowSource, final AbstractMethodSummary flow,
			final Taint taint, final Stmt stmt) {
		return flowMatchesTaintInternal(flowSource, flow, taint, stmt, this::matchShiftRight);
	}

	protected Tristate flowMatchesTaintInternal(final AbstractFlowSinkSource flowSource,
			final AbstractMethodSummary flow, final Taint taint, final Stmt stmt, MatchFunction f) {
		// Matches parameter
		boolean match = flowSource.isParameter() && taint.isParameter()
				&& taint.getParameterIndex() == flowSource.getParameterIndex();
		// Flows from a field can either be applied to the same field or the base object
		// in total
		match = match || flowSource.isField() && (taint.isGapBaseObject() || taint.isField());
		// We can have a flow from a local or a field
		match = match || flowSource.isThis() && taint.isField();
		// A value can also flow from the return value of a gap to somewhere
		match = match
				|| flowSource.isReturn() && taint.isReturn() && flowSource.getGap() != null && taint.getGap() != null;
		// For aliases, we over-approximate flows from the return edge to all possible
		// exit nodes
		match = match
				|| flowSource.isReturn() && flowSource.getGap() == null && taint.getGap() == null && taint.isReturn();
		if (!match || !compareFields(taint, flowSource))
			return Tristate.FALSE();

		return f.match(flowSource, flow, taint, stmt);
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy