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

soot.jimple.infoflow.AbstractInfoflow Maven / Gradle / Ivy

The newest version!
package soot.jimple.infoflow;

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import heros.solver.Pair;
import soot.ArrayType;
import soot.Body;
import soot.BooleanType;
import soot.ByteType;
import soot.CharType;
import soot.DoubleType;
import soot.FastHierarchy;
import soot.FloatType;
import soot.G;
import soot.IntType;
import soot.Local;
import soot.LocalGenerator;
import soot.LongType;
import soot.MethodOrMethodContext;
import soot.MethodSource;
import soot.PackManager;
import soot.PatchingChain;
import soot.PointsToAnalysis;
import soot.RefType;
import soot.Scene;
import soot.ShortType;
import soot.SootClass;
import soot.SootMethod;
import soot.SootMethodRef;
import soot.Type;
import soot.Unit;
import soot.Value;
import soot.javaToJimple.DefaultLocalGenerator;
import soot.jimple.AssignStmt;
import soot.jimple.DynamicInvokeExpr;
import soot.jimple.InvokeExpr;
import soot.jimple.Jimple;
import soot.jimple.Stmt;
import soot.jimple.infoflow.InfoflowConfiguration.AccessPathConfiguration;
import soot.jimple.infoflow.InfoflowConfiguration.CallgraphAlgorithm;
import soot.jimple.infoflow.InfoflowConfiguration.CodeEliminationMode;
import soot.jimple.infoflow.InfoflowConfiguration.DataFlowDirection;
import soot.jimple.infoflow.InfoflowConfiguration.DataFlowSolver;
import soot.jimple.infoflow.InfoflowConfiguration.PathConfiguration;
import soot.jimple.infoflow.InfoflowConfiguration.SolverConfiguration;
import soot.jimple.infoflow.InfoflowConfiguration.SootIntegrationMode;
import soot.jimple.infoflow.InfoflowConfiguration.StaticFieldTrackingMode;
import soot.jimple.infoflow.aliasing.Aliasing;
import soot.jimple.infoflow.aliasing.BackwardsFlowSensitiveAliasStrategy;
import soot.jimple.infoflow.aliasing.IAliasingStrategy;
import soot.jimple.infoflow.aliasing.NullAliasStrategy;
import soot.jimple.infoflow.cfg.BiDirICFGFactory;
import soot.jimple.infoflow.cfg.DefaultBiDiICFGFactory;
import soot.jimple.infoflow.cfg.FlowDroidSinkStatement;
import soot.jimple.infoflow.cfg.FlowDroidSourceStatement;
import soot.jimple.infoflow.cfg.FlowDroidUserClass;
import soot.jimple.infoflow.cfg.LibraryClassPatcher;
import soot.jimple.infoflow.codeOptimization.DeadCodeEliminator;
import soot.jimple.infoflow.codeOptimization.ICodeOptimizer;
import soot.jimple.infoflow.config.IInfoflowConfig;
import soot.jimple.infoflow.data.Abstraction;
import soot.jimple.infoflow.data.AbstractionAtSink;
import soot.jimple.infoflow.data.FlowDroidMemoryManager;
import soot.jimple.infoflow.data.pathBuilders.DefaultPathBuilderFactory;
import soot.jimple.infoflow.data.pathBuilders.IAbstractionPathBuilder;
import soot.jimple.infoflow.data.pathBuilders.IAbstractionPathBuilder.OnPathBuilderResultAvailable;
import soot.jimple.infoflow.data.pathBuilders.IPathBuilderFactory;
import soot.jimple.infoflow.entryPointCreators.DefaultEntryPointCreator;
import soot.jimple.infoflow.entryPointCreators.IEntryPointCreator;
import soot.jimple.infoflow.entryPointCreators.SimulatedCodeElementTag;
import soot.jimple.infoflow.globalTaints.GlobalTaintManager;
import soot.jimple.infoflow.handlers.PostAnalysisHandler;
import soot.jimple.infoflow.handlers.PreAnalysisHandler;
import soot.jimple.infoflow.handlers.ResultsAvailableHandler;
import soot.jimple.infoflow.handlers.ResultsAvailableHandler2;
import soot.jimple.infoflow.handlers.SequentialTaintPropagationHandler;
import soot.jimple.infoflow.handlers.TaintPropagationHandler;
import soot.jimple.infoflow.ipc.DefaultIPCManager;
import soot.jimple.infoflow.ipc.IIPCManager;
import soot.jimple.infoflow.memory.FlowDroidMemoryWatcher;
import soot.jimple.infoflow.memory.FlowDroidTimeoutWatcher;
import soot.jimple.infoflow.memory.IMemoryBoundedSolver;
import soot.jimple.infoflow.memory.ISolverTerminationReason;
import soot.jimple.infoflow.memory.reasons.AbortRequestedReason;
import soot.jimple.infoflow.memory.reasons.OutOfMemoryReason;
import soot.jimple.infoflow.memory.reasons.TimeoutReason;
import soot.jimple.infoflow.nativeCallHandler.BackwardNativeCallHandler;
import soot.jimple.infoflow.nativeCallHandler.INativeCallHandler;
import soot.jimple.infoflow.problems.AbstractInfoflowProblem;
import soot.jimple.infoflow.problems.BackwardsAliasProblem;
import soot.jimple.infoflow.problems.BackwardsInfoflowProblem;
import soot.jimple.infoflow.problems.TaintPropagationResults;
import soot.jimple.infoflow.problems.TaintPropagationResults.OnTaintPropagationResultAdded;
import soot.jimple.infoflow.problems.rules.IPropagationRuleManagerFactory;
import soot.jimple.infoflow.results.InfoflowPerformanceData;
import soot.jimple.infoflow.results.InfoflowResults;
import soot.jimple.infoflow.results.ResultSinkInfo;
import soot.jimple.infoflow.results.ResultSourceInfo;
import soot.jimple.infoflow.river.ConditionalFlowPostProcessor;
import soot.jimple.infoflow.river.ConditionalFlowSourceSinkManagerWrapper;
import soot.jimple.infoflow.river.EmptyUsageContextProvider;
import soot.jimple.infoflow.river.IConditionalFlowManager;
import soot.jimple.infoflow.river.IUsageContextProvider;
import soot.jimple.infoflow.river.SecondaryFlowGenerator;
import soot.jimple.infoflow.river.SecondaryFlowListener;
import soot.jimple.infoflow.solver.DefaultSolverPeerGroup;
import soot.jimple.infoflow.solver.IInfoflowSolver;
import soot.jimple.infoflow.solver.ISolverPeerGroup;
import soot.jimple.infoflow.solver.PredecessorShorteningMode;
import soot.jimple.infoflow.solver.cfg.BackwardsInfoflowCFG;
import soot.jimple.infoflow.solver.cfg.IInfoflowCFG;
import soot.jimple.infoflow.solver.executors.InterruptableExecutor;
import soot.jimple.infoflow.solver.fastSolver.InfoflowSolver;
import soot.jimple.infoflow.solver.gcSolver.GCSolverPeerGroup;
import soot.jimple.infoflow.solver.memory.DefaultMemoryManagerFactory;
import soot.jimple.infoflow.solver.memory.IMemoryManager;
import soot.jimple.infoflow.solver.memory.IMemoryManagerFactory;
import soot.jimple.infoflow.solver.sparseSolver.SparseInfoflowSolver;
import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkDefinition;
import soot.jimple.infoflow.sourcesSinks.definitions.MethodSourceSinkDefinition;
import soot.jimple.infoflow.sourcesSinks.manager.DefaultSourceSinkManager;
import soot.jimple.infoflow.sourcesSinks.manager.IOneSourceAtATimeManager;
import soot.jimple.infoflow.sourcesSinks.manager.ISourceSinkManager;
import soot.jimple.infoflow.sourcesSinks.manager.SinkInfo;
import soot.jimple.infoflow.sourcesSinks.manager.SourceInfo;
import soot.jimple.infoflow.taintWrappers.ITaintPropagationWrapper;
import soot.jimple.infoflow.threading.DefaultExecutorFactory;
import soot.jimple.infoflow.threading.IExecutorFactory;
import soot.jimple.infoflow.util.SootMethodRepresentationParser;
import soot.jimple.infoflow.util.SystemClassHandler;
import soot.jimple.toolkits.callgraph.ReachableMethods;
import soot.jimple.toolkits.pointer.DumbPointerAnalysis;
import soot.options.Options;
import soot.util.NumberedString;

/**
 * Abstract base class for all data/information flow analyses in FlowDroid
 * 
 * @author Steven Arzt
 *
 */
public abstract class AbstractInfoflow implements IInfoflow {

	protected final Logger logger = LoggerFactory.getLogger(getClass());

	protected InfoflowResults results = null;
	protected InfoflowManager manager;
	protected ISolverPeerGroup solverPeerGroup;

	protected IPathBuilderFactory pathBuilderFactory;
	protected InfoflowConfiguration config = new InfoflowConfiguration();
	protected ITaintPropagationWrapper taintWrapper;
	protected INativeCallHandler nativeCallHandler;
	protected IIPCManager ipcManager = new DefaultIPCManager(new ArrayList());

	protected final BiDirICFGFactory icfgFactory;
	protected Collection preProcessors = new ArrayList<>();
	protected Collection postProcessors = new ArrayList<>();

	protected final File androidPath;
	protected final boolean forceAndroidJar;
	protected IInfoflowConfig sootConfig;
	protected FastHierarchy hierarchy;

	protected IMemoryManagerFactory memoryManagerFactory = new DefaultMemoryManagerFactory();
	protected IExecutorFactory executorFactory = new DefaultExecutorFactory();
	protected IPropagationRuleManagerFactory ruleManagerFactory = initializeRuleManagerFactory();
	protected IPropagationRuleManagerFactory reverseRuleManagerFactory = initializeReverseRuleManagerFactory();

	protected Set collectedSources;
	protected Set collectedSinks;

	protected SootMethod dummyMainMethod;
	protected Collection additionalEntryPointMethods;

	protected boolean throwExceptions;

	protected Set onResultsAvailable = new HashSet<>();
	protected TaintPropagationHandler taintPropagationHandler = null;
	protected TaintPropagationHandler aliasPropagationHandler = null;
	protected TaintPropagationHandler reverseTaintPropagationHandler = null;
	protected TaintPropagationHandler reverseAliasPropagationHandler = null;

	protected IUsageContextProvider usageContextProvider = null;

	protected FlowDroidMemoryWatcher memoryWatcher = null;

	/**
	 * Creates a new instance of the abstract info flow problem
	 */
	public AbstractInfoflow() {
		this(null, null, false);
	}

	/**
	 * Creates a new instance of the abstract info flow problem
	 * 
	 * @param icfgFactory     The interprocedural CFG to be used by the
	 *                        InfoFlowProblem
	 * @param androidPath     If forceAndroidJar is false, this is the base
	 *                        directory of the platform files in the Android SDK. If
	 *                        forceAndroidJar is true, this is the full path of a
	 *                        single android.jar file.
	 * @param forceAndroidJar True if a single platform JAR file shall be forced,
	 *                        false if Soot shall pick the appropriate platform
	 *                        version
	 */
	public AbstractInfoflow(BiDirICFGFactory icfgFactory, File androidPath, boolean forceAndroidJar) {
		if (icfgFactory == null) {
			DefaultBiDiICFGFactory factory = new DefaultBiDiICFGFactory();
			factory.setIsAndroid(androidPath != null && androidPath.exists());
			this.icfgFactory = factory;
		} else
			this.icfgFactory = icfgFactory;
		this.androidPath = androidPath;
		this.forceAndroidJar = forceAndroidJar;
	}

	@Override
	public InfoflowConfiguration getConfig() {
		return this.config;
	}

	@Override
	public void setConfig(InfoflowConfiguration config) {
		this.config = config;
	}

	@Override
	public void setTaintWrapper(ITaintPropagationWrapper wrapper) {
		taintWrapper = wrapper;
	}

	@Override
	public void setNativeCallHandler(INativeCallHandler handler) {
		this.nativeCallHandler = handler;
	}

	@Override
	public ITaintPropagationWrapper getTaintWrapper() {
		return taintWrapper;
	}

	@Override
	public void addPreprocessor(PreAnalysisHandler preprocessor) {
		this.preProcessors.add(preprocessor);
	}

	@Override
	public void addPostProcessor(PostAnalysisHandler postprocessor) {
		this.postProcessors.add(postprocessor);
	}

	@Override
	public void computeInfoflow(String appPath, String libPath, IEntryPointCreator entryPointCreator,
			List sources, List sinks) {
		this.computeInfoflow(appPath, libPath, entryPointCreator, new DefaultSourceSinkManager(sources, sinks));
	}

	@Override
	public void computeInfoflow(String appPath, String libPath, Collection entryPoints,
			Collection sources, Collection sinks) {
		this.computeInfoflow(appPath, libPath, new DefaultEntryPointCreator(entryPoints),
				new DefaultSourceSinkManager(sources, sinks));
	}

	@Override
	public void computeInfoflow(String libPath, String appPath, String entryPoint, Collection sources,
			Collection sinks) {
		this.computeInfoflow(appPath, libPath, entryPoint, new DefaultSourceSinkManager(sources, sinks));
	}

	/**
	 * Appends two elements to build a classpath
	 * 
	 * @param appPath The first entry of the classpath
	 * @param libPath The second entry of the classpath
	 * @return The concatenated classpath
	 */
	private String appendClasspath(String appPath, String libPath) {
		String s = (appPath != null && !appPath.isEmpty()) ? appPath : "";

		if (libPath != null && !libPath.isEmpty()) {
			if (!s.isEmpty())
				s += File.pathSeparator;
			s += libPath;
		}
		return s;
	}

	/**
	 * Initializes Soot.
	 * 
	 * @param appPath The application path containing the analysis client
	 * @param libPath The Soot classpath containing the libraries
	 * @param classes The set of classes that shall be checked for data flow
	 *                analysis seeds. All sources in these classes are used as
	 *                seeds.
	 */
	protected void initializeSoot(String appPath, String libPath, Collection classes) {
		initializeSoot(appPath, libPath, classes, "");
	}

	/**
	 * Initializes Soot.
	 * 
	 * @param appPath The application path containing the analysis client
	 * @param libPath The Soot classpath containing the libraries
	 * @param classes The set of classes that shall be checked for data flow
	 *                analysis seeds. All sources in these classes are used as
	 *                seeds. If a non-empty extra seed is given, this one is used
	 *                too.
	 */
	protected void initializeSoot(String appPath, String libPath, Collection classes, String extraSeed) {
		if (config.getSootIntegrationMode().needsToInitializeSoot()) {
			// reset Soot:
			logger.info("Resetting Soot...");
			soot.G.reset();

			Options.v().set_no_bodies_for_excluded(true);
			Options.v().set_allow_phantom_refs(true);
			if (config.getWriteOutputFiles())
				Options.v().set_output_format(Options.output_format_jimple);
			else
				Options.v().set_output_format(Options.output_format_none);

			// We only need to distinguish between application and library classes
			// if we use the OnTheFly ICFG
			if (config.getCallgraphAlgorithm() == CallgraphAlgorithm.OnDemand) {
				Options.v().set_soot_classpath(libPath);
				if (appPath != null) {
					List processDirs = new LinkedList();
					for (String ap : appPath.split(File.pathSeparator))
						processDirs.add(ap);
					Options.v().set_process_dir(processDirs);
				}
			} else
				Options.v().set_soot_classpath(appendClasspath(appPath, libPath));

			// do not merge variables (causes problems with PointsToSets)
			Options.v().setPhaseOption("jb.ulp", "off");

			setSourcePrec();
		}

		if (config.getSootIntegrationMode().needsToBuildCallgraph()) {
			// Configure the callgraph algorithm
			switch (config.getCallgraphAlgorithm()) {
			case AutomaticSelection:
				// If we analyze a distinct entry point which is not static,
				// SPARK fails due to the missing allocation site and we fall
				// back to CHA.
				if (extraSeed == null || extraSeed.isEmpty()) {
					setSparkOptions();
				} else {
					setChaOptions();
				}
				break;
			case CHA:
				setChaOptions();
				break;
			case RTA:
				Options.v().setPhaseOption("cg.spark", "on");
				Options.v().setPhaseOption("cg.spark", "rta:true");
				Options.v().setPhaseOption("cg.spark", "on-fly-cg:false");
				Options.v().setPhaseOption("cg.spark", "string-constants:true");
				break;
			case VTA:
				Options.v().setPhaseOption("cg.spark", "on");
				Options.v().setPhaseOption("cg.spark", "vta:true");
				Options.v().setPhaseOption("cg.spark", "string-constants:true");
				break;
			case SPARK:
				setSparkOptions();
				break;
			case GEOM:
				setSparkOptions();
				setGeomPtaSpecificOptions();
				break;
			case OnDemand:
				// nothing to set here
				break;
			default:
				throw new RuntimeException("Invalid callgraph algorithm");
			}

			// Specify additional options required for the callgraph
			if (config.getCallgraphAlgorithm() != CallgraphAlgorithm.OnDemand) {
				Options.v().set_whole_program(true);
				Options.v().setPhaseOption("cg", "trim-clinit:false");
				if (config.getEnableReflection())
					Options.v().setPhaseOption("cg", "types-for-invoke:true");
			}
		}

		if (config.getSootIntegrationMode().needsToInitializeSoot()) {
			// at the end of setting: load user settings:
			if (sootConfig != null)
				sootConfig.setSootOptions(Options.v(), config);

			// load all entryPoint classes with their bodies
			for (String className : classes)
				Scene.v().addBasicClass(className, SootClass.BODIES);
			Scene.v().loadNecessaryClasses();
			logger.info("Basic class loading done.");

			boolean hasClasses = false;
			for (String className : classes) {
				SootClass c = Scene.v().forceResolve(className, SootClass.BODIES);
				if (c != null) {
					c.setApplicationClass();
					if (!c.isPhantomClass() && !c.isPhantom())
						hasClasses = true;
				}
			}
			if (!hasClasses) {
				logger.error("Only phantom classes loaded, skipping analysis...");
				return;
			}
		}
	}

	protected void setSourcePrec() {
		if (this.androidPath != null) {
			Options.v().set_src_prec(Options.src_prec_apk_class_jimple);
			if (this.forceAndroidJar)
				soot.options.Options.v().set_force_android_jar(this.androidPath.getAbsolutePath());
			else
				soot.options.Options.v().set_android_jars(this.androidPath.getAbsolutePath());
		} else
			Options.v().set_src_prec(Options.src_prec_java);
	}

	private void setChaOptions() {
		Options.v().setPhaseOption("cg.cha", "on");
	}

	private void setSparkOptions() {
		Options.v().setPhaseOption("cg.spark", "on");
		Options.v().setPhaseOption("cg.spark", "string-constants:true");
	}

	public static void setGeomPtaSpecificOptions() {
		Options.v().setPhaseOption("cg.spark", "geom-pta:true");

		// Those are default options, not sure whether removing them works.
		Options.v().setPhaseOption("cg.spark", "geom-encoding:Geom");
		Options.v().setPhaseOption("cg.spark", "geom-worklist:PQ");
	}

	@Override
	public void setSootConfig(IInfoflowConfig config) {
		sootConfig = config;
	}

	@Override
	public void setIPCManager(IIPCManager ipcManager) {
		this.ipcManager = ipcManager;
	}

	@Override
	public void setPathBuilderFactory(IPathBuilderFactory factory) {
		this.pathBuilderFactory = factory;
	}

	/**
	 * Constructs the callgraph
	 */
	protected void constructCallgraph() {
		if (config.getSootIntegrationMode().needsToBuildCallgraph()) {
			// Allow the ICC manager to change the Soot Scene before we continue
			if (ipcManager != null)
				ipcManager.updateJimpleForICC();

			// We might need to patch invokedynamic instructions
			if (config.isPatchInvokeDynamicInstructions())
				patchDynamicInvokeInstructions();

			// Run the preprocessors
			for (PreAnalysisHandler tr : preProcessors)
				tr.onBeforeCallgraphConstruction();

			// Patch the system libraries we need for callgraph construction
			LibraryClassPatcher patcher = getLibraryClassPatcher();
			patcher.patchLibraries();

			hierarchy = Scene.v().getOrMakeFastHierarchy();

			// To cope with broken APK files, we convert all classes that are still
			// dangling after resolution into phantoms
			for (SootClass sc : Scene.v().getClasses())
				if (sc.resolvingLevel() == SootClass.DANGLING) {
					sc.setResolvingLevel(SootClass.BODIES);
					sc.setPhantomClass();
				}

			// We explicitly select the packs we want to run for performance
			// reasons. Do not re-run the callgraph algorithm if the host
			// application already provides us with a CG.
			if (config.getCallgraphAlgorithm() != CallgraphAlgorithm.OnDemand && !Scene.v().hasCallGraph()) {
				PackManager.v().getPack("wjpp").apply();
				PackManager.v().getPack("cg").apply();
			}
		}

		// If we don't have a FastHierarchy, we need to create it - even if we use an
		// existing callgraph
		hierarchy = Scene.v().getOrMakeFastHierarchy();

		if (config.getSootIntegrationMode().needsToBuildCallgraph()) {
			// Run the preprocessors
			for (PreAnalysisHandler tr : preProcessors)
				tr.onAfterCallgraphConstruction();
		}
	}

	/**
	 * Re-writes dynamic invocation instructions into traditional invcations
	 */
	private void patchDynamicInvokeInstructions() {
		for (SootClass sc : Scene.v().getClasses()) {
			for (SootMethod sm : sc.getMethods()) {
				if (sm.hasActiveBody()) {
					Body body = sm.getActiveBody();
					patchDynamicInvokeInstructions(body);
				} else if (!(sm.getSource() instanceof MethodSourceInjector) && sm.getSource() != null) {
					sm.setSource(new MethodSourceInjector(sm.getSource()) {

						@Override
						protected void onMethodSourceLoaded(SootMethod m, Body b) {
							patchDynamicInvokeInstructions(b);
						}

					});
				}
			}
		}
	}

	/**
	 * Patches the dynamic invocation instructions in the given method body
	 * 
	 * @param body The method body in which to patch the dynamic invocation
	 *             instructions
	 */
	protected static void patchDynamicInvokeInstructions(Body body) {
		for (Iterator unitIt = body.getUnits().snapshotIterator(); unitIt.hasNext();) {
			Stmt stmt = (Stmt) unitIt.next();
			if (stmt.containsInvokeExpr()) {
				InvokeExpr iexpr = stmt.getInvokeExpr();
				if (iexpr instanceof DynamicInvokeExpr) {
					DynamicInvokeExpr diexpr = (DynamicInvokeExpr) iexpr;
					SootMethodRef bsmRef = diexpr.getBootstrapMethodRef();
					List newStmts = null;
					switch (bsmRef.getDeclaringClass().getName()) {
					case "java.lang.invoke.StringConcatFactory":
						newStmts = patchStringConcatInstruction(stmt, diexpr, body);
					}
					if (newStmts != null && !newStmts.isEmpty())
						body.getUnits().insertAfter(newStmts, stmt);
				}
			}
		}
	}

	/**
	 * Wrapper around a method source to be notified when a new body is loaded
	 * 
	 * @author Steven Arzt
	 *
	 */
	private abstract static class MethodSourceInjector implements MethodSource {

		private MethodSource innerSource;

		public MethodSourceInjector(MethodSource innerSource) {
			this.innerSource = innerSource;
		}

		@Override
		public Body getBody(SootMethod m, String phaseName) {
			Body b = innerSource.getBody(m, phaseName);
			onMethodSourceLoaded(m, b);
			return b;
		}

		protected abstract void onMethodSourceLoaded(SootMethod m, Body b);

	}

	/**
	 * Patches a specific string concatenation instruction
	 * 
	 * @param callSite The call site of the concatenation instruction
	 * @param diexpr   The dynamic invocation instruction
	 * @param b        The body that contains the dynamic invocation instruction
	 * @return The new statements that must be added after the dynamic invocation
	 *         instruction
	 */
	private static List patchStringConcatInstruction(Stmt callSite, DynamicInvokeExpr diexpr, Body b) {
		final String SIG_CONCAT_CONSTANTS = "java.lang.invoke.CallSite makeConcatWithConstants(java.lang.invoke.MethodHandles$Lookup,java.lang.String,java.lang.invoke.MethodType,java.lang.String,java.lang.Object[])";
		final String SIG_CONCAT = "java.lang.invoke.CallSite makeConcatWithConstants(java.lang.invoke.MethodHandles$Lookup,java.lang.String,java.lang.invoke.MethodType)";

		Scene scene = Scene.v();
		Jimple jimple = Jimple.v();
		LocalGenerator lg = new DefaultLocalGenerator(b);

		RefType rtStringBuilder = RefType.v("java.lang.StringBuilder");
		SootClass scStringBuilder = rtStringBuilder.getSootClass();
		SootMethodRef appendObjectRef = scene.makeMethodRef(scStringBuilder,
				"java.lang.StringBuilder append(java.lang.Object)", false);
		SootMethodRef toStringRef = scene.makeMethodRef(scene.getObjectType().getSootClass(),
				"java.lang.String toString()", false);

		SootClass scArrays = Scene.v().getSootClass("java.util.Arrays");

		List newStmts = new ArrayList<>();
		NumberedString calleeSubSig = diexpr.getBootstrapMethodRef().getSubSignature();
		if (calleeSubSig.equals(scene.getSubSigNumberer().findOrAdd(SIG_CONCAT_CONSTANTS))
				|| calleeSubSig.equals(scene.getSubSigNumberer().findOrAdd(SIG_CONCAT))) {
			// We initialize a StringBuilder
			Local sb = lg.generateLocal(rtStringBuilder);

			Stmt stmt = jimple.newAssignStmt(sb, jimple.newNewExpr(rtStringBuilder));
			stmt.addTag(SimulatedCodeElementTag.TAG);
			newStmts.add(stmt);

			stmt = jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(sb,
					scene.makeMethodRef(scStringBuilder, "void (java.lang.String)", false)));
			stmt.addTag(SimulatedCodeElementTag.TAG);
			newStmts.add(stmt);

			// Add all partial strings
			for (int i = 0; i < diexpr.getArgCount(); i++) {
				// Call toString() on the argument
				Value arg = diexpr.getArg(i);
				Type argType = arg.getType();
				SootMethodRef appendRef;
				if (argType instanceof RefType)
					appendRef = appendObjectRef;
				else if (argType instanceof ByteType)
					appendRef = scene.makeMethodRef(scStringBuilder, "java.lang.StringBuilder append(byte)", false);
				else if (argType instanceof BooleanType)
					appendRef = scene.makeMethodRef(scStringBuilder, "java.lang.StringBuilder append(boolean)", false);
				else if (argType instanceof CharType)
					appendRef = scene.makeMethodRef(scStringBuilder, "java.lang.StringBuilder append(char)", false);
				else if (argType instanceof ShortType)
					appendRef = scene.makeMethodRef(scStringBuilder, "java.lang.StringBuilder append(short)", false);
				else if (argType instanceof IntType)
					appendRef = scene.makeMethodRef(scStringBuilder, "java.lang.StringBuilder append(int)", false);
				else if (argType instanceof LongType)
					appendRef = scene.makeMethodRef(scStringBuilder, "java.lang.StringBuilder append(long)", false);
				else if (argType instanceof FloatType)
					appendRef = scene.makeMethodRef(scStringBuilder, "java.lang.StringBuilder append(float)", false);
				else if (argType instanceof DoubleType)
					appendRef = scene.makeMethodRef(scStringBuilder, "java.lang.StringBuilder append(double)", false);
				else if (argType instanceof ArrayType) {
					// For an array argument, we need to Arrays.toString() first
					ArrayType at = (ArrayType) argType;
					Type elementType = at.getElementType();

					Local sarg = lg.generateLocal(RefType.v("java.lang.String"));
					SootMethodRef elementToStringRef = null;
					if (elementType instanceof RefType)
						elementToStringRef = scene.makeMethodRef(scArrays,
								"java.lang.String toString(java.lang.Object[])", true);
					else if (elementType instanceof ByteType)
						elementToStringRef = scene.makeMethodRef(scArrays, "java.lang.String toString(byte[])", true);
					else if (elementType instanceof BooleanType)
						elementToStringRef = scene.makeMethodRef(scArrays, "java.lang.String toString(boolean[])",
								true);
					else if (elementType instanceof CharType)
						elementToStringRef = scene.makeMethodRef(scArrays, "java.lang.String toString(char[])", true);
					else if (elementType instanceof ShortType)
						elementToStringRef = scene.makeMethodRef(scArrays, "java.lang.String toString(short[])", true);
					else if (elementType instanceof IntType)
						elementToStringRef = scene.makeMethodRef(scArrays, "java.lang.String toString(int[])", true);
					else if (elementType instanceof LongType)
						elementToStringRef = scene.makeMethodRef(scArrays, "java.lang.String toString(long[])", true);
					else if (elementType instanceof FloatType)
						elementToStringRef = scene.makeMethodRef(scArrays, "java.lang.String toString(float[])", true);
					else if (elementType instanceof DoubleType)
						elementToStringRef = scene.makeMethodRef(scArrays, "java.lang.String toString(double[])", true);
					else {
						throw new RuntimeException(String.format(
								"Invalid array element type %s for string concatenation in dynamic invocation",
								elementType.toString()));
					}

					Stmt toStringStmt = jimple.newAssignStmt(sarg,
							jimple.newStaticInvokeExpr(elementToStringRef, Collections.singletonList(arg)));
					toStringStmt.addTag(SimulatedCodeElementTag.TAG);
					newStmts.add(toStringStmt);

					arg = sarg;
					appendRef = scStringBuilder.getMethod("java.lang.StringBuilder append(java.lang.String)").makeRef();
				} else {
					throw new RuntimeException(String.format(
							"Invalid type %s for string concatenation in dynamic invocation", argType.toString()));
				}

				stmt = jimple.newInvokeStmt(jimple.newVirtualInvokeExpr(sb, appendRef, Collections.singletonList(arg)));
				stmt.addTag(SimulatedCodeElementTag.TAG);
				newStmts.add(stmt);
			}

			// Obtain the result
			if (callSite instanceof AssignStmt) {
				AssignStmt assignStmt = (AssignStmt) callSite;
				stmt = jimple.newAssignStmt((Local) assignStmt.getLeftOp(),
						jimple.newVirtualInvokeExpr(sb, toStringRef));
				stmt.addTag(SimulatedCodeElementTag.TAG);
				newStmts.add(stmt);
			}
		}
		return newStmts;
	}

	protected LibraryClassPatcher getLibraryClassPatcher() {
		return new LibraryClassPatcher();
	}

	@Override
	public void setMemoryManagerFactory(IMemoryManagerFactory factory) {
		this.memoryManagerFactory = factory;
	}

	@Override
	public void computeInfoflow(String appPath, String libPath, IEntryPointCreator entryPointCreator,
			ISourceSinkManager sourcesSinks) {
		if (sourcesSinks == null) {
			logger.error("SourceSinkManager not specified");
			return;
		}

		if (config.getSootIntegrationMode() != SootIntegrationMode.UseExistingInstance)
			initializeSoot(appPath, libPath, entryPointCreator.getRequiredClasses());

		// entryPoints are the entryPoints required by Soot to calculate Graph -
		// if there is no main method, we have to create a new main method and
		// use it as entryPoint and store our real entryPoints
		this.dummyMainMethod = entryPointCreator.createDummyMain();
		this.additionalEntryPointMethods = entryPointCreator.getAdditionalMethods();
		Scene.v().setEntryPoints(Collections.singletonList(dummyMainMethod));

		// Run the analysis
		runAnalysis(sourcesSinks, null);
	}

	@Override
	public void computeInfoflow(String appPath, String libPath, String entryPoint, ISourceSinkManager sourcesSinks) {
		if (sourcesSinks == null) {
			logger.error("No source/sink manager specified");
			return;
		}

		initializeSoot(appPath, libPath, SootMethodRepresentationParser.v()
				.parseClassNames(Collections.singletonList(entryPoint), false).keySet(), entryPoint);

		if (!Scene.v().containsMethod(entryPoint)) {
			logger.error("Entry point not found: " + entryPoint);
			return;
		}
		SootMethod ep = Scene.v().getMethod(entryPoint);
		if (ep.isConcrete())
			ep.retrieveActiveBody();
		else {
			logger.debug("Skipping non-concrete method " + ep);
			return;
		}
		this.dummyMainMethod = null;
		Scene.v().setEntryPoints(Collections.singletonList(ep));
		Options.v().set_main_class(ep.getDeclaringClass().getName());

		// Compute the additional seeds if they are specified
		Set seeds = Collections.emptySet();
		if (entryPoint != null && !entryPoint.isEmpty())
			seeds = Collections.singleton(entryPoint);
		ipcManager.updateJimpleForICC();

		// Run the analysis
		runAnalysis(sourcesSinks, seeds);
	}

	/**
	 * Conducts a taint analysis on an already initialized callgraph
	 * 
	 * @param sourcesSinks The sources and sinks to be used
	 */
	protected void runAnalysis(final ISourceSinkManager sourcesSinks) {
		runAnalysis(sourcesSinks, null);
	}

	/**
	 * Conducts a taint analysis on an already initialized callgraph
	 * 
	 * @param sourcesSinks    The sources and sinks to be used
	 * @param additionalSeeds Additional seeds at which to create A ZERO fact even
	 *                        if they are not sources
	 */
	protected void runAnalysis(final ISourceSinkManager sourcesSinks, final Set additionalSeeds) {
		final InfoflowPerformanceData performanceData = createPerformanceDataClass();
		try {
			// Clear the data from previous runs
			results = createResultsObject();
			results.setPerformanceData(performanceData);

			// Print and check our configuration
			checkAndFixConfiguration();
			config.printSummary();

			// Register a memory watcher
			if (memoryWatcher != null) {
				memoryWatcher.clearSolvers();
				memoryWatcher = null;
			}
			memoryWatcher = new FlowDroidMemoryWatcher(results, config.getMemoryThreshold());

			// Initialize the abstraction configuration
			Abstraction.initialize(config);

			if (taintWrapper != null)
				preProcessors.addAll(taintWrapper.getPreAnalysisHandlers());

			// Build the callgraph
			long beforeCallgraph = System.nanoTime();
			constructCallgraph();
			performanceData
					.setCallgraphConstructionSeconds((int) Math.round((System.nanoTime() - beforeCallgraph) / 1E9));
			logger.info(String.format(Locale.getDefault(), "Callgraph construction took %d seconds",
					performanceData.getCallgraphConstructionSeconds()));

			// Initialize the source sink manager
			if (sourcesSinks != null)
				sourcesSinks.initialize();

			// Perform constant propagation and remove dead code
			if (config.getCodeEliminationMode() != CodeEliminationMode.NoCodeElimination) {
				long currentMillis = System.nanoTime();
				eliminateDeadCode(sourcesSinks);
				logger.info("Dead code elimination took " + (System.nanoTime() - currentMillis) / 1E9 + " seconds");
			}

			// After constant value propagation, we might find more call edges
			// for reflective method calls
			if (config.getEnableReflection()) {
				releaseCallgraph();
				constructCallgraph();
			}

			if (config.getCallgraphAlgorithm() != CallgraphAlgorithm.OnDemand)
				logger.info("Callgraph has {} edges", Scene.v().getCallGraph().size());

			IInfoflowCFG iCfg = icfgFactory.buildBiDirICFG(config.getCallgraphAlgorithm(),
					config.getEnableExceptionTracking());

			if (config.isTaintAnalysisEnabled())
				runTaintAnalysis(sourcesSinks, additionalSeeds, iCfg, performanceData);

			// Gather performance data
			performanceData.setTotalRuntimeSeconds((int) Math.round((System.nanoTime() - beforeCallgraph) / 1E9));
			performanceData.updateMaxMemoryConsumption(getUsedMemory());
			logger.info(String.format("Data flow solver took %d seconds. Maximum memory consumption: %d MB",
					performanceData.getTotalRuntimeSeconds(), performanceData.getMaxMemoryConsumption()));

			// Provide the handler with the final results
			for (ResultsAvailableHandler handler : onResultsAvailable)
				handler.onResultsAvailable(iCfg, results);

			// Write the Jimple files to disk if requested
			if (config.getWriteOutputFiles())
				PackManager.v().writeOutput();
		} catch (Exception ex) {
			StringWriter stacktrace = new StringWriter();
			PrintWriter pw = new PrintWriter(stacktrace);
			ex.printStackTrace(pw);
			if (results != null)
				results.addException(ex.getClass().getName() + ": " + ex.getMessage() + "\n" + stacktrace.toString());
			logger.error("Exception during data flow analysis", ex);
			if (throwExceptions)
				throw ex;
		}
	}

	private void runTaintAnalysis(final ISourceSinkManager sourcesSinks, final Set additionalSeeds,
			IInfoflowCFG iCfg, InfoflowPerformanceData performanceData) {
		logger.info("Starting Taint Analysis");

		// Make sure that we have a path builder factory
		if (pathBuilderFactory == null)
			pathBuilderFactory = new DefaultPathBuilderFactory(config.getPathConfiguration());

		// Check whether we need to run with one source at a time
		IOneSourceAtATimeManager oneSourceAtATime = config.getOneSourceAtATime() && sourcesSinks != null
				&& sourcesSinks instanceof IOneSourceAtATimeManager ? (IOneSourceAtATimeManager) sourcesSinks : null;

		// Reset the current source
		if (oneSourceAtATime != null)
			oneSourceAtATime.resetCurrentSource();
		boolean hasMoreSources = oneSourceAtATime == null || oneSourceAtATime.hasNextSource();

		while (hasMoreSources) {
			// Fetch the next source
			if (oneSourceAtATime != null)
				oneSourceAtATime.nextSource();

			// Create the executor that takes care of the workers
			int numThreads = Runtime.getRuntime().availableProcessors();
			InterruptableExecutor executor = executorFactory.createExecutor(numThreads, true, config);
			executor.setThreadFactory(new ThreadFactory() {

				@Override
				public Thread newThread(Runnable r) {
					Thread thrIFDS = new Thread(r);
					thrIFDS.setDaemon(true);
					thrIFDS.setName("FlowDroid");
					return thrIFDS;
				}

			});

			// Initialize the memory manager
			IMemoryManager memoryManager = createMemoryManager();

			// Initialize our infrastructure for global taints
			final Set solvers = new HashSet<>();
			GlobalTaintManager globalTaintManager = new GlobalTaintManager(solvers);

			// Initialize the data flow manager
			manager = initializeInfoflowManager(sourcesSinks, iCfg, globalTaintManager);

			// Create the solver peer group
			switch (manager.getConfig().getSolverConfiguration().getDataFlowSolver()) {
			case FineGrainedGC:
				solverPeerGroup = new GCSolverPeerGroup>();
				break;
			case GarbageCollecting:
				solverPeerGroup = new GCSolverPeerGroup();
				break;
			default:
				solverPeerGroup = new DefaultSolverPeerGroup();
				break;
			}

			// Initialize the alias analysis
			Abstraction zeroValue = Abstraction.getZeroAbstraction(manager.getConfig().getFlowSensitiveAliasing());
			IAliasingStrategy aliasingStrategy = createAliasAnalysis(sourcesSinks, iCfg, executor, memoryManager);

			IInfoflowSolver backwardSolver = aliasingStrategy.getSolver();
			if (backwardSolver != null) {
				zeroValue = backwardSolver.getTabulationProblem().createZeroValue();
				solvers.add(backwardSolver);
			}

			// Initialize the aliasing infrastructure
			Aliasing aliasing = createAliasController(aliasingStrategy);
			if (dummyMainMethod != null)
				aliasing.excludeMethodFromMustAlias(dummyMainMethod);
			manager.setAliasing(aliasing);

			// Initialize the data flow problem
			AbstractInfoflowProblem forwardProblem = createInfoflowProblem(zeroValue);

			// We need to create the right data flow solver
			IInfoflowSolver forwardSolver = createDataFlowSolver(executor, forwardProblem);

			// Set the options
			manager.setMainSolver(forwardSolver);
			if (aliasingStrategy.getSolver() != null)
				aliasingStrategy.getSolver().getTabulationProblem().getManager().setMainSolver(forwardSolver);
			solvers.add(forwardSolver);

			memoryWatcher.addSolver((IMemoryBoundedSolver) forwardSolver);

			forwardSolver.setMemoryManager(memoryManager);
			// forwardSolver.setEnableMergePointChecking(true);

			forwardProblem.setTaintPropagationHandler(taintPropagationHandler);
			forwardProblem.setTaintWrapper(taintWrapper);
			if (nativeCallHandler != null)
				forwardProblem.setNativeCallHandler(nativeCallHandler);

			if (aliasingStrategy.getSolver() != null) {
				aliasingStrategy.getSolver().getTabulationProblem().setActivationUnitsToCallSites(forwardProblem);
				manager.setAliasSolver(aliasingStrategy.getSolver());
			}

			IInfoflowSolver additionalSolver = null;
			IInfoflowSolver additionalAliasSolver = null;
			INativeCallHandler additionalNativeCallHandler = null;
			if (config.getAdditionalFlowsEnabled()) {
				// Add the SecondaryFlowGenerator to the main forward taint analysis
				TaintPropagationHandler forwardHandler = forwardProblem.getTaintPropagationHandler();
				if (forwardHandler != null) {
					if (forwardHandler instanceof SequentialTaintPropagationHandler) {
						((SequentialTaintPropagationHandler) forwardHandler).addHandler(new SecondaryFlowGenerator());
					} else {
						SequentialTaintPropagationHandler seqTpg = new SequentialTaintPropagationHandler();
						seqTpg.addHandler(forwardHandler);
						seqTpg.addHandler(new SecondaryFlowGenerator());
						forwardProblem.setTaintPropagationHandler(seqTpg);
					}
				} else {
					forwardProblem.setTaintPropagationHandler(new SecondaryFlowGenerator());
				}

				if (!(manager.getSourceSinkManager() instanceof IConditionalFlowManager))
					throw new IllegalStateException("Additional Flows enabled but no ConditionalFlowManager in place!");

				// Additional flows get their taints injected dependent on the flow of the taint
				// anlaysis.
				// Thus, we don't have any taints before.
				InfoflowManager additionalManager = new InfoflowManager(config, null, new BackwardsInfoflowCFG(iCfg),
						new ConditionalFlowSourceSinkManagerWrapper(
								(IConditionalFlowManager) manager.getSourceSinkManager()),
						taintWrapper, hierarchy, globalTaintManager);

				AbstractInfoflowProblem additionalProblem = new BackwardsInfoflowProblem(additionalManager, zeroValue,
						reverseRuleManagerFactory);

				additionalSolver = createDataFlowSolver(executor, additionalProblem);
				additionalManager.setMainSolver(additionalSolver);
				additionalSolver.setMemoryManager(memoryManager);
				memoryWatcher.addSolver((IMemoryBoundedSolver) additionalSolver);

				// Set all handlers to the additional problem
				additionalProblem.setTaintPropagationHandler(new SecondaryFlowListener());
				additionalProblem.setTaintWrapper(taintWrapper);
				additionalNativeCallHandler = new BackwardNativeCallHandler();
				additionalProblem.setNativeCallHandler(additionalNativeCallHandler);

				// Initialize the alias analysis
				IAliasingStrategy revereAliasingStrategy = createBackwardAliasAnalysis(additionalManager, sourcesSinks,
						iCfg, executor, memoryManager);
				if (revereAliasingStrategy.getSolver() != null)
					revereAliasingStrategy.getSolver().getTabulationProblem().getManager()
							.setMainSolver(additionalSolver);

				additionalAliasSolver = revereAliasingStrategy.getSolver();

				// Initialize the aliasing infrastructure
				Aliasing reverseAliasing = createAliasController(revereAliasingStrategy);
				if (dummyMainMethod != null)
					reverseAliasing.excludeMethodFromMustAlias(dummyMainMethod);
				additionalManager.setAliasing(reverseAliasing);
				additionalManager.setAliasSolver(additionalAliasSolver);

				manager.additionalManager = additionalManager;

				// Add the post processor if necessary
				if (config.getFilterConditionalSinks())
					addPostProcessor(new ConditionalFlowPostProcessor(manager));

				// If the user did not provide an UsageContextProvider, provide the default
				// implementation
				if (usageContextProvider == null)
					usageContextProvider = new EmptyUsageContextProvider();
				manager.setUsageContextProvider(usageContextProvider);
				additionalManager.setUsageContextProvider(usageContextProvider);
			}

			// Start a thread for enforcing the timeout
			FlowDroidTimeoutWatcher timeoutWatcher = null;
			FlowDroidTimeoutWatcher pathTimeoutWatcher = null;
			if (config.getDataFlowTimeout() > 0) {
				timeoutWatcher = new FlowDroidTimeoutWatcher(config.getDataFlowTimeout(), results);
				timeoutWatcher.addSolver((IMemoryBoundedSolver) forwardSolver);
				if (aliasingStrategy.getSolver() != null)
					timeoutWatcher.addSolver((IMemoryBoundedSolver) aliasingStrategy.getSolver());
				if (additionalSolver != null)
					timeoutWatcher.addSolver((IMemoryBoundedSolver) additionalSolver);
				if (additionalAliasSolver != null)
					timeoutWatcher.addSolver((IMemoryBoundedSolver) additionalAliasSolver);
				timeoutWatcher.start();
			}

			InterruptableExecutor resultExecutor = null;
			long beforePathReconstruction = 0;
			try {
				// Print our configuration
				if (config.getFlowSensitiveAliasing() && !aliasingStrategy.isFlowSensitive())
					logger.warn("Trying to use a flow-sensitive aliasing with an "
							+ "aliasing strategy that does not support this feature");
				if (config.getFlowSensitiveAliasing()
						&& config.getSolverConfiguration().getMaxJoinPointAbstractions() > 0)
					logger.warn("Running with limited join point abstractions can break context-"
							+ "sensitive path builders");

				// We have to look through the complete program to find
				// sources which are then taken as seeds.
				int sinkCount = 0;
				logger.info("Looking for sources and sinks...");

				for (SootMethod sm : getMethodsForSeeds(iCfg))
					sinkCount += scanMethodForSourcesSinks(sourcesSinks, forwardProblem, sm);

				// We optionally also allow additional seeds to be specified
				if (additionalSeeds != null)
					for (String meth : additionalSeeds) {
						SootMethod m = Scene.v().getMethod(meth);
						if (!m.hasActiveBody()) {
							logger.warn("Seed method {} has no active body", m);
							continue;
						}
						forwardProblem.addInitialSeeds(m.getActiveBody().getUnits().getFirst(),
								Collections.singleton(forwardProblem.zeroValue()));
					}

				// Report on the sources and sinks we have found
				if (!forwardProblem.hasInitialSeeds()) {
					logger.error("No sources found, aborting analysis");
					continue;
				}
				if (sinkCount == 0) {
					logger.error("No sinks found, aborting analysis");
					continue;
				}
				logger.info("Source lookup done, found {} sources and {} sinks.",
						forwardProblem.getInitialSeeds().size(), sinkCount);

				// Update the performance statistics
				performanceData.setSourceCount(forwardProblem.getInitialSeeds().size());
				performanceData.setSinkCount(sinkCount);

				// Initialize the taint wrapper if we have one
				if (taintWrapper != null)
					taintWrapper.initialize(manager);
				if (nativeCallHandler != null)
					nativeCallHandler.initialize(manager);
				if (additionalNativeCallHandler != null)
					additionalNativeCallHandler.initialize(manager);

				// Register the handler for interim results
				TaintPropagationResults propagationResults = forwardProblem.getResults();
				resultExecutor = executorFactory.createExecutor(numThreads, false, config);
				resultExecutor.setThreadFactory(new ThreadFactory() {

					@Override
					public Thread newThread(Runnable r) {
						return createNewThread(r);
					}
				});

				// Create the path builder
				final IAbstractionPathBuilder builder = createPathBuilder(resultExecutor);
				// final IAbstractionPathBuilder builder = new
				// DebuggingPathBuilder(pathBuilderFactory, manager);

				// If we want incremental result reporting, we have to
				// initialize it before we start the taint tracking
				if (config.getIncrementalResultReporting())
					initializeIncrementalResultReporting(propagationResults, builder);

				// Initialize the performance data
				if (performanceData.getTaintPropagationSeconds() < 0)
					performanceData.setTaintPropagationSeconds(0);
				long beforeTaintPropagation = System.nanoTime();

				onBeforeTaintPropagation(forwardSolver, backwardSolver);
				forwardSolver.solve();

				// Not really nice, but sometimes Heros returns before all
				// executor tasks are actually done. This way, we give it a
				// chance to terminate gracefully before moving on.
				int terminateTries = 0;
				while (terminateTries < 10) {
					if (executor.getActiveCount() != 0 || !executor.isTerminated()) {
						terminateTries++;
						try {
							Thread.sleep(500);
						} catch (InterruptedException e) {
							logger.error("Could not wait for executor termination", e);
						}
					} else
						break;
				}
				if (executor.getActiveCount() != 0 || !executor.isTerminated())
					logger.error("Executor did not terminate gracefully");
				if (executor.getException() != null) {
					throw new RuntimeException("An exception has occurred in an executor", executor.getException());
				}

				// Update performance statistics
				performanceData.updateMaxMemoryConsumption(getUsedMemory());
				int taintPropagationSeconds = (int) Math.round((System.nanoTime() - beforeTaintPropagation) / 1E9);
				performanceData.addTaintPropagationSeconds(taintPropagationSeconds);
				performanceData.addEdgePropagationCount(forwardSolver.getPropagationCount());
				performanceData.setInfoflowPropagationCount(forwardSolver.getPropagationCount());
				if (backwardSolver != null) {
					performanceData.setAliasPropagationCount(backwardSolver.getPropagationCount());
					performanceData.addEdgePropagationCount(backwardSolver.getPropagationCount());
				}

				// Print taint wrapper statistics
				if (taintWrapper != null) {
					logger.info("Taint wrapper hits: " + taintWrapper.getWrapperHits());
					logger.info("Taint wrapper misses: " + taintWrapper.getWrapperMisses());
				}

				// Give derived classes a chance to do whatever they need before we remove stuff
				// from memory
				onTaintPropagationCompleted(forwardSolver, backwardSolver, additionalSolver, additionalAliasSolver);

				// Get the result abstractions
				Set res = propagationResults.getResults();
				propagationResults = null;

				// We need to prune access paths that are entailed by
				// another one
				if (config.getDataFlowDirection() != DataFlowDirection.Backwards)
					removeEntailedAbstractions(res);

				if (config.getAdditionalFlowsEnabled()) {
					res = new HashSet<>(res);
					Set additionalRes = manager.additionalManager.getMainSolver()
							.getTabulationProblem().getResults().getResults();
					res.addAll(additionalRes);
				}

				// Shut down the native call handler
				if (nativeCallHandler != null)
					nativeCallHandler.shutdown();
				if (additionalNativeCallHandler != null)
					additionalNativeCallHandler.shutdown();

				if (config.getAdditionalFlowsEnabled()) {
					logger.info(
							"IFDS problem with {} forward, {} backward, {} additional backward and {} additional"
									+ " forward edges, solved in {} seconds, processing {} results...",
							forwardSolver.getPropagationCount(),
							aliasingStrategy.getSolver() == null ? 0
									: aliasingStrategy.getSolver().getPropagationCount(),
							additionalSolver == null ? 0 : additionalSolver.getPropagationCount(),
							additionalAliasSolver == null ? 0 : additionalAliasSolver.getPropagationCount(),
							taintPropagationSeconds, res == null ? 0 : res.size());
				} else {
					logger.info(
							"IFDS problem with {} forward and {} backward edges solved in {} seconds, "
									+ "processing {} results...",
							forwardSolver.getPropagationCount(),
							aliasingStrategy.getSolver() == null ? 0
									: aliasingStrategy.getSolver().getPropagationCount(),
							taintPropagationSeconds, res == null ? 0 : res.size());
				}

				// Update the statistics
				{
					ISolverTerminationReason reason = ((IMemoryBoundedSolver) forwardSolver).getTerminationReason();
					if (reason != null) {
						if (reason instanceof OutOfMemoryReason)
							results.setTerminationState(
									results.getTerminationState() | InfoflowResults.TERMINATION_DATA_FLOW_OOM);
						else if (reason instanceof TimeoutReason)
							results.setTerminationState(
									results.getTerminationState() | InfoflowResults.TERMINATION_DATA_FLOW_TIMEOUT);
					}
				}

				// Force a cleanup. Everything we need is reachable through
				// the results set, the other abstractions can be killed
				// now.
				performanceData.updateMaxMemoryConsumption(getUsedMemory());
				logger.info(String.format("Current memory consumption: %d MB", getUsedMemory()));

				if (timeoutWatcher != null)
					timeoutWatcher.stop();
				memoryWatcher.removeSolver((IMemoryBoundedSolver) forwardSolver);
				forwardSolver.cleanup();
				forwardSolver = null;
				forwardProblem = null;

				solverPeerGroup = null;

				// Remove the alias analysis from memory
				aliasing = null;
				if (aliasingStrategy.getSolver() != null) {
					aliasingStrategy.getSolver().terminate();
					memoryWatcher.removeSolver((IMemoryBoundedSolver) aliasingStrategy.getSolver());
				}
				aliasingStrategy.cleanup();
				aliasingStrategy = null;

				if (config.getIncrementalResultReporting())
					res = null;
				iCfg.purge();

				// Clean up possible additional flow things
				if (manager.additionalManager != null) {
					additionalSolver.cleanup();
					memoryWatcher.removeSolver((IMemoryBoundedSolver) additionalSolver);
					additionalSolver = null;

					manager.additionalManager.setAliasing(null);
					additionalAliasSolver.cleanup();
					memoryWatcher.removeSolver((IMemoryBoundedSolver) additionalAliasSolver);
					additionalAliasSolver = null;

					manager.additionalManager.cleanup();
					manager.additionalManager = null;
				}

				// Clean up the manager. Make sure to free objects, even if
				// the manager is still held by other objects
				if (manager != null)
					manager.cleanup();
				manager = null;

				// Report the remaining memory consumption
				Runtime.getRuntime().gc();
				performanceData.updateMaxMemoryConsumption(getUsedMemory());
				logger.info(String.format("Memory consumption after cleanup: %d MB", getUsedMemory()));

				// Apply the timeout to path reconstruction
				if (config.getPathConfiguration().getPathReconstructionTimeout() > 0) {
					pathTimeoutWatcher = new FlowDroidTimeoutWatcher(
							config.getPathConfiguration().getPathReconstructionTimeout(), results);
					pathTimeoutWatcher.addSolver(builder);
					pathTimeoutWatcher.start();
				}
				beforePathReconstruction = System.nanoTime();

				// Do the normal result computation in the end unless we
				// have used incremental path building
				if (config.getIncrementalResultReporting()) {
					// After the last intermediate result has been computed,
					// we need to re-process those abstractions that
					// received new neighbors in the meantime
					builder.runIncrementalPathComputation();

					try {
						resultExecutor.awaitCompletion();
					} catch (InterruptedException e) {
						logger.error("Could not wait for executor termination", e);
					}
				} else {
					memoryWatcher.addSolver(builder);
					builder.computeTaintPaths(res);
					res = null;

					// Wait for the path builders to terminate
					try {
						// The path reconstruction should stop on time anyway. In case it doesn't, we
						// make sure that we don't get stuck.
						long pathTimeout = config.getPathConfiguration().getPathReconstructionTimeout();
						if (pathTimeout > 0)
							resultExecutor.awaitCompletion(pathTimeout + 20, TimeUnit.SECONDS);
						else
							resultExecutor.awaitCompletion();
					} catch (InterruptedException e) {
						logger.error("Could not wait for executor termination", e);
					}

					// Update the statistics
					{
						ISolverTerminationReason reason = builder.getTerminationReason();
						if (reason != null) {
							if (reason instanceof OutOfMemoryReason)
								results.setTerminationState(results.getTerminationState()
										| InfoflowResults.TERMINATION_PATH_RECONSTRUCTION_OOM);
							else if (reason instanceof TimeoutReason)
								results.setTerminationState(results.getTerminationState()
										| InfoflowResults.TERMINATION_PATH_RECONSTRUCTION_TIMEOUT);
						}
					}

					// Get the results once the path builder is done
					this.results.addAll(builder.getResults());
				}
				resultExecutor.shutdown();

				// If the path builder was aborted, we warn the user
				if (builder.isKilled())
					logger.warn("Path reconstruction aborted. The reported results may be incomplete. "
							+ "You might want to try again with sequential path processing enabled.");
			} finally {
				// Terminate the executor
				if (resultExecutor != null)
					resultExecutor.shutdown();

				// Make sure to stop the watcher thread
				if (timeoutWatcher != null)
					timeoutWatcher.stop();
				if (pathTimeoutWatcher != null)
					pathTimeoutWatcher.stop();

				if (aliasingStrategy != null) {
					IInfoflowSolver solver = aliasingStrategy.getSolver();
					if (solver != null)
						solver.terminate();
				}

				// Do we have any more sources?
				hasMoreSources = oneSourceAtATime != null && oneSourceAtATime.hasNextSource();

				// Shut down the memory watcher
				memoryWatcher.close();

				// Get rid of all the stuff that's still floating around in
				// memory
				forwardProblem = null;
				forwardSolver = null;
				if (manager != null)
					manager.cleanup();
				manager = null;
			}

			// Make sure that we are in a sensible state even if we ran out
			// of memory before
			Runtime.getRuntime().gc();
			performanceData.updateMaxMemoryConsumption((int) getUsedMemory());
			performanceData.setPathReconstructionSeconds(
					(int) Math.round((System.nanoTime() - beforePathReconstruction) / 1E9));

			logger.info(String.format("Memory consumption after path building: %d MB", getUsedMemory()));
			logger.info(String.format("Path reconstruction took %d seconds",
					performanceData.getPathReconstructionSeconds()));
		}

		// Execute the post-processors
		for (PostAnalysisHandler handler : this.postProcessors)
			results = handler.onResultsAvailable(results, iCfg);

		if (results == null || results.isEmpty())
			logger.warn("No results found.");
		else if (logger.isInfoEnabled()) {
			for (ResultSinkInfo sink : results.getResults().keySet()) {
				logger.info("The sink {} in method {} was called with values from the following sources:", sink,
						iCfg.getMethodOf(sink.getStmt()).getSignature());
				for (ResultSourceInfo source : results.getResults().get(sink)) {
					logger.info("- {} in method {}", source, iCfg.getMethodOf(source.getStmt()).getSignature());
					if (source.getPath() != null) {
						logger.info("\ton Path: ");
						for (Unit p : source.getPath()) {
							if (p != null) {
								logger.info("\t -> " + iCfg.getMethodOf(p));
								int ln = p.getJavaSourceStartLineNumber();
								logger.info("\t\t -> " + p + (ln != -1 ? " in line " + ln : ""));
							}
						}
					}
				}
			}
		}
	}

	protected Thread createNewThread(Runnable r) {
		Thread thrPath = new Thread(r);
		thrPath.setDaemon(true);
		thrPath.setName("FlowDroid Path Reconstruction");
		return thrPath;
	}

	/**
	 * Creates the problem for the IFDS taint propagation problem
	 * 
	 * @param zeroValue The taint abstraction for the tautology
	 * @return The IDFS problem
	 */
	protected abstract AbstractInfoflowProblem createInfoflowProblem(Abstraction zeroValue);

	/**
	 * Creates the reverse problem for the IFDS taint propagation problem
	 * 
	 * @param zeroValue The taint abstraction for the tautology
	 * @return The IDFS problem
	 */
	protected AbstractInfoflowProblem createReverseInfoflowProblem(InfoflowManager manager, Abstraction zeroValue) {
		return null;
	}

	/**
	 * Creates the instance of the data flow solver
	 *
	 * @param executor     The executor on which the solver shall run its tasks
	 * @param problem      The problem to be solved by the new solver
	 * @param solverConfig The solver configuration
	 * @return The new data flow solver
	 */
	protected IInfoflowSolver createDataFlowSolver(InterruptableExecutor executor, AbstractInfoflowProblem problem,
			SolverConfiguration solverConfig) {
		switch (solverConfig.getDataFlowSolver()) {
		case ContextFlowSensitive:
			logger.info("Using context- and flow-sensitive solver");
			InfoflowSolver infoflowSolver = new InfoflowSolver(problem, executor);
			solverPeerGroup.addSolver(infoflowSolver);
			return infoflowSolver;
		case SparseContextFlowSensitive:
			InfoflowConfiguration.SparsePropagationStrategy opt = config.getSolverConfiguration()
					.getSparsePropagationStrategy();
			logger.info(
					"Using sparse context-sensitive and flow-sensitive solver with sparsification " + opt.toString());
			IInfoflowSolver sparseSolver = new SparseInfoflowSolver(problem, executor, opt);
			solverPeerGroup.addSolver(sparseSolver);
			return sparseSolver;
		case FlowInsensitive:
			logger.info("Using context-sensitive, but flow-insensitive solver");
			return new soot.jimple.infoflow.solver.fastSolver.flowInsensitive.InfoflowSolver(problem, executor);
		case GarbageCollecting:
			logger.info("Using garbage-collecting solver");
			IInfoflowSolver solver = new soot.jimple.infoflow.solver.gcSolver.InfoflowSolver(problem, executor,
					solverConfig.getSleepTime());
			solverPeerGroup.addSolver(solver);
			return solver;
		case FineGrainedGC:
			logger.info("Using fine-grained garbage-collecting solver");
			IInfoflowSolver fgSolver = new soot.jimple.infoflow.solver.gcSolver.fpc.InfoflowSolver(problem, executor,
					solverConfig.getSleepTime());
			solverPeerGroup.addSolver(fgSolver);
			return fgSolver;
		default:
			throw new RuntimeException("Unsupported data flow solver");
		}
	}

	protected enum SourceSinkState {
		SOURCE, SINK, NEITHER, BOTH
	}

	protected static class SourceOrSink {
		private final SourceInfo sourceInfo;
		private final SinkInfo sinkInfo;
		private final SourceSinkState state;

		protected SourceOrSink(SourceInfo sourceInfo, SinkInfo sinkInfo) {
			this.sourceInfo = sourceInfo;
			this.sinkInfo = sinkInfo;
			if (sourceInfo != null && sinkInfo == null)
				this.state = SourceSinkState.SOURCE;
			else if (sinkInfo != null && sourceInfo == null)
				this.state = SourceSinkState.SINK;
			else if (sourceInfo != null && sinkInfo != null)
				this.state = SourceSinkState.BOTH;
			else
				this.state = SourceSinkState.NEITHER;
		}

		protected SourceSinkState getState() {
			return state;
		}

		protected SourceInfo getSourceInfo() {
			return sourceInfo;
		}

		protected SinkInfo getSinkInfo() {
			return sinkInfo;
		}

		@Override
		public String toString() {
			switch (state) {
			case SOURCE:
				return "Source";
			case SINK:
				return "Sink";
			case BOTH:
				return "Source and Sink";
			case NEITHER:
				return "Neither";
			default:
				return "Unknown. That's a bad state to be in.";
			}
		}

		@Override
		public int hashCode() {
			return Objects.hash(sinkInfo, sourceInfo, state);
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			SourceOrSink other = (SourceOrSink) obj;
			return Objects.equals(sinkInfo, other.sinkInfo) && Objects.equals(sourceInfo, other.sourceInfo)
					&& state == other.state;
		}
	}

	/**
	 * Checks whether the given source/sink definition is a callback or references
	 * the return value of a method
	 *
	 * @param definitions The source/sink definition to check
	 * @return True if the given source/sink definition references a callback or a
	 *         method return value
	 */
	private boolean isCallbackOrReturn(Collection definitions) {
		for (ISourceSinkDefinition definition : definitions) {
			if (definition instanceof MethodSourceSinkDefinition) {
				MethodSourceSinkDefinition methodDef = (MethodSourceSinkDefinition) definition;
				MethodSourceSinkDefinition.CallType callType = methodDef.getCallType();
				if (callType == MethodSourceSinkDefinition.CallType.Callback
						|| callType == MethodSourceSinkDefinition.CallType.Return)
					return true;
			}
		}
		return false;
	}

	/**
	 * Scans the given method for sources and sinks contained in it. Sinks are just
	 * counted, sources are added to the InfoflowProblem as seeds.
	 * 
	 * @param sourcesSinks   The SourceSinkManager to be used for identifying
	 *                       sources and sinks
	 * @param forwardProblem The InfoflowProblem in which to register the sources as
	 *                       seeds
	 * @param m              The method to scan for sources and sinks
	 * @return The number of sinks found in this method
	 */
	private int scanMethodForSourcesSinks(final ISourceSinkManager sourcesSinks, AbstractInfoflowProblem forwardProblem,
			SootMethod m) {
		if (getConfig().getLogSourcesAndSinks() && collectedSources == null) {
			collectedSources = new HashSet<>();
			collectedSinks = new HashSet<>();
		}

		int sinkCount = 0;
		if (m.hasActiveBody()) {
			// Check whether this is a system class we need to ignore
			if (!isValidSeedMethod(m))
				return sinkCount;

			// Look for a source in the method. Also look for sinks. If we
			// have no sink in the program, we don't need to perform any
			// analysis
			PatchingChain units = m.getActiveBody().getUnits();
			for (Unit u : units) {
				Stmt s = (Stmt) u;
				SourceOrSink sos = scanStmtForSourcesSinks(sourcesSinks, s);
				switch (sos.getState()) {
				case SOURCE:
					if (s.containsInvokeExpr() && !isCallbackOrReturn(sos.getSourceInfo().getAllDefinitions()))
						s.addTag(FlowDroidSourceStatement.INSTANCE);
					forwardProblem.addInitialSeeds(s, Collections.singleton(forwardProblem.zeroValue()));
					if (getConfig().getLogSourcesAndSinks())
						collectedSources.add(s);
					break;
				case SINK:
					if (s.containsInvokeExpr())
						s.addTag(FlowDroidSinkStatement.INSTANCE);
					if (getConfig().getLogSourcesAndSinks())
						collectedSinks.add(s);
					sinkCount++;
					break;
				case BOTH:
					if (s.containsInvokeExpr()) {
						if (!isCallbackOrReturn(sos.getSourceInfo().getAllDefinitions()))
							s.addTag(FlowDroidSourceStatement.INSTANCE);
						s.addTag(FlowDroidSinkStatement.INSTANCE);
					}
					forwardProblem.addInitialSeeds(s, Collections.singleton(forwardProblem.zeroValue()));
					if (getConfig().getLogSourcesAndSinks()) {
						collectedSources.add(s);
						collectedSinks.add(s);
					}
					sinkCount++;
					break;
				case NEITHER:
					break;
				}
			}

		}
		return sinkCount;
	}

	/**
	 * Checks whether the given statement is a source or a sink
	 *
	 * @param sourcesSinks The source/sink manager
	 * @param s            The statement to check
	 * @return An enumeration value that defines whether the given statement is a
	 *         source, a sink, or neither
	 */
	protected abstract SourceOrSink scanStmtForSourcesSinks(final ISourceSinkManager sourcesSinks, Stmt s);

	/**
	 * Given a callgraph, obtains all methods that may contain sources, i.e.,
	 * statements that can serve as seeds for the taint propagation.
	 * 
	 * @param icfg The interprocedural control flow graph
	 * @return The methods that may contain seeds
	 */
	protected Collection getMethodsForSeeds(IInfoflowCFG icfg) {
		List seeds = new LinkedList<>();
		// If we have a callgraph, we retrieve the reachable methods. Otherwise,
		// we have no choice but take all application methods as an
		// approximation
		if (Scene.v().hasCallGraph()) {
			ReachableMethods reachableMethods = Scene.v().getReachableMethods();
			reachableMethods.update();
			for (Iterator iter = reachableMethods.listener(); iter.hasNext();) {
				SootMethod sm = iter.next().method();
				if (isValidSeedMethod(sm))
					seeds.add(sm);
			}
		} else {
			long beforeSeedMethods = System.nanoTime();
			Set doneSet = new HashSet<>();
			for (SootMethod sm : Scene.v().getEntryPoints())
				getMethodsForSeedsIncremental(sm, doneSet, seeds, icfg);
			logger.info("Collecting seed methods took {} seconds", (System.nanoTime() - beforeSeedMethods) / 1E9);
		}
		return seeds;
	}

	private void getMethodsForSeedsIncremental(SootMethod sm, Set doneSet, List seeds,
			IInfoflowCFG icfg) {
		assert Scene.v().hasFastHierarchy();
		if (!sm.isConcrete() || !sm.getDeclaringClass().isApplicationClass() || !doneSet.add(sm))
			return;
		seeds.add(sm);
		for (Unit u : sm.retrieveActiveBody().getUnits()) {
			Stmt stmt = (Stmt) u;
			if (stmt.containsInvokeExpr()) {
				for (SootMethod callee : icfg.getCalleesOfCallAt(stmt)) {
					if (isValidSeedMethod(callee))
						getMethodsForSeedsIncremental(callee, doneSet, seeds, icfg);
				}
			}
		}
	}

	/**
	 * Gets whether the given method is a valid seen when scanning for sources and
	 * sinks. A method is a valid seed it it (or one of its transitive callees) can
	 * contain calls to source or sink methods.
	 *
	 * @param sm The method to check
	 * @return True if this method or one of its transitive callees can contain
	 *         sources or sinks, otherwise false
	 */
	protected boolean isValidSeedMethod(SootMethod sm) {
		if (sm == dummyMainMethod)
			return false;
		if (dummyMainMethod != null && sm.getDeclaringClass() == dummyMainMethod.getDeclaringClass())
			return false;

		// Exclude system classes
		final String className = sm.getDeclaringClass().getName();
		if (config.getIgnoreFlowsInSystemPackages() && SystemClassHandler.v().isClassInSystemPackage(className)) {
			if (isUserCodeClass(className)) {
				// Sometimes the namespace used for apps coincides with a system package prefix.
				// isUserCodeClass allows to still mark such methods as user code. To remember
				// this decision
				// without always calling isUserCodeClass (with a possible inefficient string
				// lookup), we do use
				// a tag instead.
				if (!sm.getDeclaringClass().hasTag(FlowDroidUserClass.TAG_NAME))
					sm.getDeclaringClass().addTag(FlowDroidUserClass.v());
				return true;
			} else {
				return false;
			}
		}

		// Exclude library classes
		if (config.getExcludeSootLibraryClasses() && sm.getDeclaringClass().isLibraryClass())
			return false;

		return true;
	}

	/**
	 * Checks whether the given class is user code and should not be filtered out.
	 * By default, this method assumes that all code is potentially user code.
	 *
	 * @param className The name of the class to check
	 * @return True if the given class is user code, false otherwise
	 */
	protected boolean isUserCodeClass(String className) {
		return false;
	}

	/**
	 * Runs all code optimizers
	 * 
	 * @param sourcesSinks The SourceSinkManager
	 */
	protected void eliminateDeadCode(ISourceSinkManager sourcesSinks) {
		InfoflowManager dceManager = new InfoflowManager(config, null,
				icfgFactory.buildBiDirICFG(config.getCallgraphAlgorithm(), config.getEnableExceptionTracking()));

		// Dead code elimination may drop the points-to analysis. We need to restore it.
		final Scene scene = Scene.v();
		PointsToAnalysis pta = scene.getPointsToAnalysis();

		// We need to exclude the dummy main method and all other artificial methods
		// that the entry point creator may have generated as well
		Set excludedMethods = new HashSet<>();
		if (additionalEntryPointMethods != null)
			excludedMethods.addAll(additionalEntryPointMethods);
		excludedMethods.addAll(Scene.v().getEntryPoints());

		// Allow for additional code instrumentation steps
		performCodeInstrumentationBeforeDCE(dceManager, excludedMethods);

		ICodeOptimizer dce = new DeadCodeEliminator();
		dce.initialize(config);
		dce.run(dceManager, excludedMethods, sourcesSinks, taintWrapper);

		// Restore the points-to analysis. This may restore a PAG that contains outdated
		// methods, but it's still better than running the entire callgraph algorithm
		// again. Continuing with the DumbPointerAnalysis is not a viable solution
		// either, since it heavily over-approximates.
		if (pta != null && !(pta instanceof DumbPointerAnalysis)) {
			PointsToAnalysis newPta = scene.getPointsToAnalysis();
			if (newPta == null || newPta instanceof DumbPointerAnalysis)
				scene.setPointsToAnalysis(pta);
		}

		// Allow for additional code instrumentation steps
		performCodeInstrumentationAfterDCE(dceManager, excludedMethods);
	}

	/**
	 * Allows subclasses to perform additional code instrumentation tasks
	 *
	 * @param dceManager      The manager class for dead code elimination and
	 *                        instrumentation
	 * @param excludedMethods The methods that shall not be modified
	 */
	protected void performCodeInstrumentationBeforeDCE(InfoflowManager dceManager, Set excludedMethods) {
	}

	/**
	 * Allows subclasses to perform additional code instrumentation tasks
	 * 
	 * @param dceManager      The manager class for dead code elimination and
	 *                        instrumentation
	 * @param excludedMethods The methods that shall not be modified
	 */
	protected void performCodeInstrumentationAfterDCE(InfoflowManager dceManager, Set excludedMethods) {
	}

	/**
	 * Creates the IFDS solver for the forward data flow problem
	 *
	 * @param executor The executor in which to run the tasks or propagating IFDS
	 *                 edges
	 * @param problem  The implementation of the forward problem
	 * @return The solver that solves the forward taint analysis problem
	 */
	protected IInfoflowSolver createDataFlowSolver(InterruptableExecutor executor, AbstractInfoflowProblem problem) {
		// Depending on the configured solver algorithm, we have to create a
		// different solver object
		IInfoflowSolver solver;
		SolverConfiguration solverConfig = config.getSolverConfiguration();
		solver = createDataFlowSolver(executor, problem, solverConfig);

		// Configure the solver
		solver.setSolverId(true);
		solver.setPredecessorShorteningMode(pathConfigToShorteningMode(manager.getConfig().getPathConfiguration()));
		solver.setMaxJoinPointAbstractions(solverConfig.getMaxJoinPointAbstractions());
		solver.setMaxCalleesPerCallSite(solverConfig.getMaxCalleesPerCallSite());
		solver.setMaxAbstractionPathLength(solverConfig.getMaxAbstractionPathLength());

		return solver;
	}

	/**
	 * Gets the path shortening mode that shall be applied given a certain path
	 * reconstruction configuration. This method computes the most aggressive path
	 * shortening that is possible without eliminating data that is necessary for
	 * the requested path reconstruction.
	 * 
	 * @param pathConfiguration The path reconstruction configuration
	 * @return The computed path shortening mode
	 */
	protected PredecessorShorteningMode pathConfigToShorteningMode(PathConfiguration pathConfiguration) {
		if (pathBuilderFactory.supportsPathReconstruction()) {
			switch (pathConfiguration.getPathReconstructionMode()) {
			case Fast:
				return PredecessorShorteningMode.ShortenIfEqual;
			case NoPaths:
				return PredecessorShorteningMode.AlwaysShorten;
			case Precise:
				return PredecessorShorteningMode.NeverShorten;
			default:
				throw new RuntimeException("Unknown path reconstruction mode");
			}
		} else
			return PredecessorShorteningMode.AlwaysShorten;
	}

	/**
	 * Creates the memory manager that helps reduce the memory consumption of the
	 * data flow analysis
	 *
	 * @return The memory manager object
	 */
	protected IMemoryManager createMemoryManager() {
		if (memoryManagerFactory == null)
			return null;

		FlowDroidMemoryManager.PathDataErasureMode erasureMode;
		if (config.getPathConfiguration().mustKeepStatements())
			erasureMode = FlowDroidMemoryManager.PathDataErasureMode.EraseNothing;
		else if (pathBuilderFactory.supportsPathReconstruction())
			erasureMode = FlowDroidMemoryManager.PathDataErasureMode.EraseNothing;
		else if (pathBuilderFactory.isContextSensitive())
			erasureMode = FlowDroidMemoryManager.PathDataErasureMode.KeepOnlyContextData;
		else
			erasureMode = FlowDroidMemoryManager.PathDataErasureMode.EraseAll;
		IMemoryManager memoryManager = memoryManagerFactory.getMemoryManager(false, erasureMode);
		return memoryManager;
	}

	/**
	 * Releases the callgraph and all intermediate objects associated with it
	 */
	protected void releaseCallgraph() {
		Scene.v().releaseCallGraph();
		Scene.v().releasePointsToAnalysis();
		Scene.v().releaseReachableMethods();
		G.v().resetSpark();
	}

	/**
	 * Checks the configuration of the data flow solver for errors and automatically
	 * fixes some common issues
	 */
	private void checkAndFixConfiguration() {
		final AccessPathConfiguration accessPathConfig = config.getAccessPathConfiguration();
		if (config.getStaticFieldTrackingMode() != StaticFieldTrackingMode.None
				&& accessPathConfig.getAccessPathLength() == 0)
			throw new RuntimeException("Static field tracking must be disabled if the access path length is zero");
		if (config.getSolverConfiguration().getDataFlowSolver() == DataFlowSolver.FlowInsensitive) {
			config.setFlowSensitiveAliasing(false);
			config.setEnableTypeChecking(false);
			logger.warn("Disabled flow-sensitive aliasing because we are running with "
					+ "a flow-insensitive data flow solver");
		}
		if (config.getAdditionalFlowsEnabled()
				&& config.getDataFlowDirection() == InfoflowConfiguration.DataFlowDirection.Backwards)
			throw new RuntimeException(
					"Invalid configuration: the backward direction does not support additional flows");
	}

	/**
	 * Factory method for creating the data object that will receive the data flow
	 * solver's performance data
	 * 
	 * @return The data object for the performance data
	 */
	protected InfoflowPerformanceData createPerformanceDataClass() {
		return new InfoflowPerformanceData();
	}

	@Override
	public void setExecutorFactory(IExecutorFactory executorFactory) {
		this.executorFactory = executorFactory;
	}

	@Override
	public void setPropagationRuleManagerFactory(IPropagationRuleManagerFactory ruleManagerFactory) {
		this.ruleManagerFactory = ruleManagerFactory;
	}

	@Override
	public Set getCollectedSources() {
		return this.collectedSources;
	}

	@Override
	public Set getCollectedSinks() {
		return this.collectedSinks;
	}

	/**
	 * Gets the memory used by FlowDroid at the moment
	 * 
	 * @return FlowDroid's current memory consumption in megabytes
	 */
	private int getUsedMemory() {
		Runtime runtime = Runtime.getRuntime();
		return (int) Math.round((runtime.totalMemory() - runtime.freeMemory()) / 1E6);
	}

	/**
	 * Initializes the mechanism for incremental result reporting
	 * 
	 * @param propagationResults A reference to the result object of the forward
	 *                           data flow solver
	 * @param builder            The path builder to use for reconstructing the
	 *                           taint propagation paths
	 */
	private void initializeIncrementalResultReporting(TaintPropagationResults propagationResults,
			final IAbstractionPathBuilder builder) {
		// Create the path builder
		memoryWatcher.addSolver(builder);
		this.results = createResultsObject();
		propagationResults.addResultAvailableHandler(new OnTaintPropagationResultAdded() {

			@Override
			public boolean onResultAvailable(AbstractionAtSink abs) {
				builder.addResultAvailableHandler(new OnPathBuilderResultAvailable() {

					@Override
					public void onResultAvailable(ResultSourceInfo source, ResultSinkInfo sink) {
						// Notify our external handlers
						for (ResultsAvailableHandler handler : onResultsAvailable) {
							if (handler instanceof ResultsAvailableHandler2) {
								ResultsAvailableHandler2 handler2 = (ResultsAvailableHandler2) handler;
								handler2.onSingleResultAvailable(source, sink);
							}
						}
						results.addResult(sink, source);
					}

				});

				// Compute the result paths
				builder.computeTaintPaths(Collections.singleton(abs));
				return true;
			}

		});
	}

	/**
	 * Creates a new instance of the result class appropriate for the current data
	 * flow analysis
	 * 
	 * @return The new result object
	 */
	protected abstract InfoflowResults createResultsObject();

	/**
	 * Removes all abstractions from the given set that arrive at the same sink
	 * statement as another abstraction, but cover less tainted variables. If, e.g.,
	 * a.b.* and a.* arrive at the same sink, a.b.* is already covered by a.* and
	 * can thus safely be removed.
	 * 
	 * @param res The result set from which to remove all entailed abstractions
	 */
	private void removeEntailedAbstractions(Set res) {
		for (Iterator absAtSinkIt = res.iterator(); absAtSinkIt.hasNext();) {
			AbstractionAtSink curAbs = absAtSinkIt.next();
			for (AbstractionAtSink checkAbs : res) {
				if (checkAbs != curAbs && checkAbs.getSinkStmt() == curAbs.getSinkStmt()
						&& checkAbs.getAbstraction().localEquals(curAbs.getAbstraction())
						&& checkAbs.getSinkDefinitions().equals(curAbs.getSinkDefinitions())) {
					if (checkAbs.getAbstraction().getAccessPath().entails(curAbs.getAbstraction().getAccessPath())) {
						absAtSinkIt.remove();
						break;
					}
				}
			}
		}
	}

	/**
	 * Initializes the alias analysis
	 * 
	 * @param sourcesSinks  The set of sources and sinks
	 * @param iCfg          The interprocedural control flow graph
	 * @param executor      The executor in which to run concurrent tasks
	 * @param memoryManager The memory manager for rducing the memory load during
	 *                      IFDS propagation
	 * @return The alias analysis implementation to use for the data flow analysis
	 */
	protected abstract IAliasingStrategy createAliasAnalysis(final ISourceSinkManager sourcesSinks, IInfoflowCFG iCfg,
			InterruptableExecutor executor, IMemoryManager memoryManager);

	/**
	 * Initializes the alias analysis for the backward direction
	 *
	 * @param manager       The infoflow manager
	 * @param sourcesSinks  The set of sources and sinks
	 * @param iCfg          The interprocedural control flow graph
	 * @param executor      The executor in which to run concurrent tasks
	 * @param memoryManager The memory manager for reducing the memory load during
	 *                      IFDS propagation
	 * @return The backward alias analysis implementation
	 */
	protected IAliasingStrategy createBackwardAliasAnalysis(InfoflowManager manager,
			final ISourceSinkManager sourcesSinks, IInfoflowCFG iCfg, InterruptableExecutor executor,
			IMemoryManager memoryManager) {
		IAliasingStrategy aliasingStrategy;
		IInfoflowSolver aliasSolver = null;
		BackwardsAliasProblem aliasProblem = null;
		InfoflowManager aliasManager = null;
		switch (getConfig().getAliasingAlgorithm()) {
		case FlowSensitive:
			// The original icfg is already backwards for the backwards data flow analysis
			aliasManager = new InfoflowManager(config, null, iCfg, sourcesSinks, taintWrapper, hierarchy, manager);
			aliasProblem = new BackwardsAliasProblem(aliasManager);

			// We need to create the right data flow solver
			SolverConfiguration solverConfig = config.getSolverConfiguration();
			aliasSolver = createDataFlowSolver(executor, aliasProblem, solverConfig);

			aliasSolver.setMemoryManager(memoryManager);
			aliasSolver.setPredecessorShorteningMode(
					pathConfigToShorteningMode(manager.getConfig().getPathConfiguration()));
			// aliasSolver.setEnableMergePointChecking(true);
			aliasSolver.setMaxJoinPointAbstractions(solverConfig.getMaxJoinPointAbstractions());
			aliasSolver.setMaxCalleesPerCallSite(solverConfig.getMaxCalleesPerCallSite());
			aliasSolver.setMaxAbstractionPathLength(solverConfig.getMaxAbstractionPathLength());
			aliasSolver.setSolverId(false);
			aliasProblem.setTaintPropagationHandler(aliasPropagationHandler);
			aliasProblem.setTaintWrapper(taintWrapper);
			if (nativeCallHandler != null)
				aliasProblem.setNativeCallHandler(nativeCallHandler);

			memoryWatcher.addSolver((IMemoryBoundedSolver) aliasSolver);

			aliasingStrategy = new BackwardsFlowSensitiveAliasStrategy(manager, aliasSolver);
			break;
		case None:
			aliasProblem = null;
			aliasSolver = null;
			aliasingStrategy = new NullAliasStrategy();
			break;
		default:
			throw new RuntimeException("Unsupported aliasing algorithm: " + getConfig().getAliasingAlgorithm());
		}
		return aliasingStrategy;
	}

	/**
	 * Creates the controller object that handles aliasing operations. Derived
	 * classes can override this method to supply custom aliasing implementations.
	 * 
	 * @param aliasingStrategy The aliasing strategy to use
	 * @return The new alias controller object
	 */
	protected Aliasing createAliasController(IAliasingStrategy aliasingStrategy) {
		return new Aliasing(aliasingStrategy, manager);
	}

	@Override
	public void addResultsAvailableHandler(ResultsAvailableHandler handler) {
		this.onResultsAvailable.add(handler);
	}

	@Override
	public void setTaintPropagationHandler(TaintPropagationHandler handler) {
		this.taintPropagationHandler = handler;
	}

	@Override
	public void setAliasPropagationHandler(TaintPropagationHandler handler) {
		this.aliasPropagationHandler = handler;
	}

	@Override
	public void removeResultsAvailableHandler(ResultsAvailableHandler handler) {
		onResultsAvailable.remove(handler);
	}

	@Override
	public InfoflowResults getResults() {
		return results;
	}

	@Override
	public boolean isResultAvailable() {
		if (results == null) {
			return false;
		}
		return true;
	}

	@Override
	public void abortAnalysis() {
		ISolverTerminationReason reason = new AbortRequestedReason();

		if (manager != null) {
			// Stop the forward taint analysis
			IInfoflowSolver forwardSolver = manager.getMainSolver();
			if (forwardSolver instanceof IMemoryBoundedSolver) {
				IMemoryBoundedSolver boundedSolver = (IMemoryBoundedSolver) forwardSolver;
				boundedSolver.forceTerminate(reason);
			}

			// Stop the alias analysis
			IInfoflowSolver backwardSolver = manager.getAliasSolver();
			if (backwardSolver instanceof IMemoryBoundedSolver) {
				IMemoryBoundedSolver boundedSolver = (IMemoryBoundedSolver) backwardSolver;
				boundedSolver.forceTerminate(reason);
			}

			if (manager.additionalManager != null) {
				IInfoflowSolver additionalSolver = manager.additionalManager.getMainSolver();
				if (additionalSolver instanceof IMemoryBoundedSolver) {
					IMemoryBoundedSolver boundedSolver = (IMemoryBoundedSolver) additionalSolver;
					boundedSolver.forceTerminate(reason);
				}

				IInfoflowSolver additionalAliasSolver = manager.additionalManager.getAliasSolver();
				if (additionalAliasSolver instanceof IMemoryBoundedSolver) {
					IMemoryBoundedSolver boundedSolver = (IMemoryBoundedSolver) additionalAliasSolver;
					boundedSolver.forceTerminate(reason);
				}
			}
		}

		if (memoryWatcher != null) {
			// Stop all registered solvers
			memoryWatcher.forceTerminate(reason);
		}
	}

	public void setThrowExceptions(boolean b) {
		this.throwExceptions = b;
	}

	/**
	 * Initializes the data flow manager with which propagation rules can interact
	 * with the data flow engine
	 * 
	 * @param sourcesSinks       The source/sink definitions
	 * @param iCfg               The interprocedural control flow graph
	 * @param globalTaintManager The manager object for storing and processing
	 *                           global taints
	 * @return The data flow manager
	 */
	protected abstract InfoflowManager initializeInfoflowManager(final ISourceSinkManager sourcesSinks,
			IInfoflowCFG iCfg, GlobalTaintManager globalTaintManager);

	protected InfoflowManager initializeReverseInfoflowManager(IInfoflowCFG iCfg,
			GlobalTaintManager globalTaintManager) {
		return null;
	}

	/**
	 * Callback that is invoked when the main taint propagation is about to start
	 * 
	 * @param forwardSolver  The forward data flow solver
	 * @param backwardSolver The backward data flow solver
	 */
	protected void onBeforeTaintPropagation(IInfoflowSolver forwardSolver, IInfoflowSolver backwardSolver) {
		//
	}

	/**
	 * Callback that is invoked when the main taint propagation has completed. This
	 * method is called before memory cleanup happens.
	 * 
	 * @param forwardSolver  The forward data flow solver
	 * @param backwardSolver The backward data flow solver
	 */
	protected void onTaintPropagationCompleted(IInfoflowSolver forwardSolver, IInfoflowSolver aliasSolver,
			IInfoflowSolver backwardSolver, IInfoflowSolver backwardAliasSolver) {
		//
	}

	/**
	 * Creates the path builder that shall be used for path reconstruction
	 * 
	 * @param executor The execute in which to run the parallel path reconstruction
	 *                 tasks
	 * @return The path builder implementation
	 */
	protected IAbstractionPathBuilder createPathBuilder(InterruptableExecutor executor) {
		return pathBuilderFactory.createPathBuilder(manager, executor);
	}

	/**
	 * Initializes an appropriate instance of the rule manager factory
	 * 
	 * @return The rule manager factory
	 */
	protected abstract IPropagationRuleManagerFactory initializeRuleManagerFactory();

	/**
	 * Initializes an appropriate instance of the rule manager factory
	 * 
	 * @return The rule manager factory
	 */
	protected IPropagationRuleManagerFactory initializeReverseRuleManagerFactory() {
		return null;
	};

	@Override
	public void setUsageContextProvider(IUsageContextProvider usageContextProvider) {
		this.usageContextProvider = usageContextProvider;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy