From d3bbf8603d5453f8546d5cfd8e53f80684696b34 Mon Sep 17 00:00:00 2001 From: Marc Miltenberger Date: Mon, 26 Jan 2026 11:06:36 +0100 Subject: [PATCH 1/8] Fix a bug in FlowDroid with reused locals --- .../jimple/infoflow/AbstractInfoflow.java | 61 +++++++++++++++- .../infoflow/FlowDroidLocalSplitter.java | 59 ++++++++++++++++ .../soot/jimple/infoflow/data/AccessPath.java | 10 +++ .../results/AbstractResultSourceSinkInfo.java | 16 ++++- .../infoflow/results/ResultSourceInfo.java | 10 +++ .../infoflow/test/junit/JUnitTests.java | 69 ++++++++++++++++++- 6 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 soot-infoflow/src/soot/jimple/infoflow/FlowDroidLocalSplitter.java diff --git a/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java b/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java index 193c09958..ccc643777 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java +++ b/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java @@ -55,7 +55,9 @@ import soot.jimple.InvokeExpr; import soot.jimple.Jimple; import soot.jimple.Stmt; +import soot.jimple.infoflow.FlowDroidLocalSplitter.SplittedLocal; import soot.jimple.infoflow.InfoflowConfiguration.AccessPathConfiguration; +import soot.jimple.infoflow.InfoflowConfiguration.AliasingAlgorithm; import soot.jimple.infoflow.InfoflowConfiguration.CallgraphAlgorithm; import soot.jimple.infoflow.InfoflowConfiguration.CodeEliminationMode; import soot.jimple.infoflow.InfoflowConfiguration.DataFlowDirection; @@ -512,16 +514,22 @@ protected void constructCallgraph() { // To cope with broken APK files, we convert all classes that are still // dangling after resolution into phantoms - for (SootClass sc : Scene.v().getClasses()) + 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()) { + if (config.getAliasingAlgorithm() == AliasingAlgorithm.PtsBased) { + //we need to split here already for the PTS to work correctly + splitAllBodies(); + } + PackManager.v().getPack("wjpp").apply(); PackManager.v().getPack("cg").apply(); } @@ -911,8 +919,14 @@ protected void runAnalysis(final ISourceSinkManager sourcesSinks, final Set it = body.getUseAndDefBoxesIterator(); + while (it.hasNext()) { + ValueBox box = it.next(); + Value val = box.getValue(); + if (val instanceof SplittedLocal) { + SplittedLocal l = (SplittedLocal) val; + box.setValue(l.getOriginalLocal()); + } + } + Iterator lit = body.getLocals().iterator(); + while (lit.hasNext()) { + if (lit.next() instanceof SplittedLocal) { + lit.remove(); + } + } + } + } + } + } + + //With newer soot versions, locals are reused more often, which + //can be a problem for FlowDroid. So, we split the locals prior to + //running FlowDroid. + protected void splitAllBodies() { + FlowDroidLocalSplitter splitter = FlowDroidLocalSplitter.v(); + for (SootClass sc : new ArrayList<>(Scene.v().getApplicationClasses())) { + for (SootMethod m : new ArrayList<>(sc.getMethods())) { + if (m.isConcrete()) { + splitter.transform(m.retrieveActiveBody()); + } + } + } + } + /** * Since these simulations are not perfect, we should remove them afterwards */ diff --git a/soot-infoflow/src/soot/jimple/infoflow/FlowDroidLocalSplitter.java b/soot-infoflow/src/soot/jimple/infoflow/FlowDroidLocalSplitter.java new file mode 100644 index 000000000..94be7c879 --- /dev/null +++ b/soot-infoflow/src/soot/jimple/infoflow/FlowDroidLocalSplitter.java @@ -0,0 +1,59 @@ +package soot.jimple.infoflow; + +import soot.Local; +import soot.Singletons.Global; +import soot.jimple.internal.JimpleLocal; +import soot.toolkits.scalar.LocalSplitter; + +/** + * With more recent soot versions, locals are reused more often. + * This can cause problems in FlowDroid (e.g. the overwriteParameter test case). + * The simple solution: We split these locals beforehand + * @author Marc Miltenberger + */ +public class FlowDroidLocalSplitter extends LocalSplitter { + public static class SplittedLocal extends JimpleLocal { + + private static final long serialVersionUID = 1L; + private JimpleLocal originalLocal; + + public SplittedLocal(JimpleLocal oldLocal) { + super(null, oldLocal.getType()); + //do not intern the name again + setName(oldLocal.getName()); + if (oldLocal.isUserDefinedLocal()) { + setUserDefinedLocal(); + } + + this.originalLocal = oldLocal; + while (originalLocal instanceof SplittedLocal) { + originalLocal = ((SplittedLocal) originalLocal).originalLocal; + } + } + + public JimpleLocal getOriginalLocal() { + return originalLocal; + } + + } + + public FlowDroidLocalSplitter() { + super((Global) null); + } + + @Override + protected String getNewName(String name, int count) { + //Reuse the old name + return name; + } + + @Override + protected Local createClonedLocal(Local oldLocal) { + return new SplittedLocal((JimpleLocal) oldLocal); + } + + public static FlowDroidLocalSplitter v() { + return new FlowDroidLocalSplitter(); + } + +} diff --git a/soot-infoflow/src/soot/jimple/infoflow/data/AccessPath.java b/soot-infoflow/src/soot/jimple/infoflow/data/AccessPath.java index 0a897d622..637b52a39 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/data/AccessPath.java +++ b/soot-infoflow/src/soot/jimple/infoflow/data/AccessPath.java @@ -586,4 +586,14 @@ public static AccessPath getZeroAccessPath() { return zeroAccessPath; } + /** + * Creates a new access path that is effectively the same, but based on another local as plain value + * @param newValue the new value + * @return the rebased access path + */ + public AccessPath rebaseTo(Local newValue) { + return new AccessPath(newValue, baseType, fragments, taintSubFields, cutOffApproximation, arrayTaintType, + canHaveImmutableAliases); + } + } diff --git a/soot-infoflow/src/soot/jimple/infoflow/results/AbstractResultSourceSinkInfo.java b/soot-infoflow/src/soot/jimple/infoflow/results/AbstractResultSourceSinkInfo.java index e96b1b124..269b75fb5 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/results/AbstractResultSourceSinkInfo.java +++ b/soot-infoflow/src/soot/jimple/infoflow/results/AbstractResultSourceSinkInfo.java @@ -1,9 +1,12 @@ package soot.jimple.infoflow.results; +import soot.Local; import soot.jimple.Stmt; +import soot.jimple.infoflow.FlowDroidLocalSplitter.SplittedLocal; import soot.jimple.infoflow.InfoflowConfiguration; import soot.jimple.infoflow.data.AccessPath; import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkDefinition; +import soot.jimple.internal.JimpleLocal; /** * Abstract base class for information on data flow results @@ -35,7 +38,7 @@ public AbstractResultSourceSinkInfo(ISourceSinkDefinition definition, AccessPath assert accessPath != null; this.definition = definition; - this.accessPath = accessPath; + this.accessPath = replaceAccessPath(accessPath); this.stmt = stmt; this.userData = userData; } @@ -97,4 +100,15 @@ public boolean equals(Object o) { return true; } + protected AccessPath replaceAccessPath(AccessPath accessPath) { + Local pv = accessPath.getPlainValue(); + if (pv instanceof SplittedLocal) { + SplittedLocal s = (SplittedLocal) pv; + JimpleLocal orig = s.getOriginalLocal(); + + AccessPath a = accessPath.rebaseTo(orig); + return a; + } + return accessPath; + } } diff --git a/soot-infoflow/src/soot/jimple/infoflow/results/ResultSourceInfo.java b/soot-infoflow/src/soot/jimple/infoflow/results/ResultSourceInfo.java index 1bc024300..08ebabfad 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/results/ResultSourceInfo.java +++ b/soot-infoflow/src/soot/jimple/infoflow/results/ResultSourceInfo.java @@ -46,6 +46,7 @@ public ResultSourceInfo(ISourceSinkDefinition definition, AccessPath source, Stm this.pathCallSites = pathCallSites == null || pathCallSites.isEmpty() ? null : pathCallSites.toArray(new Stmt[pathCallSites.size()]); this.pathAgnosticResults = pathAgnosticResults; + this.replaceSplitLocalsWithOriginals(); } public ResultSourceInfo(ISourceSinkDefinition definition, AccessPath source, Stmt context, Object userData, @@ -56,6 +57,7 @@ public ResultSourceInfo(ISourceSinkDefinition definition, AccessPath source, Stm this.pathAPs = pathAPs; this.pathCallSites = pathCallSites; this.pathAgnosticResults = pathAgnosticResults; + this.replaceSplitLocalsWithOriginals(); } public Stmt[] getPath() { @@ -97,6 +99,14 @@ public int hashCode() { return result; } + private void replaceSplitLocalsWithOriginals() { + if (pathAPs != null) { + for (int i = 0; i < pathAPs.length; i++) { + pathAPs[i] = replaceAccessPath(pathAPs[i]); + } + } + } + @Override public boolean equals(Object obj) { if (this == obj) diff --git a/soot-infoflow/test/soot/jimple/infoflow/test/junit/JUnitTests.java b/soot-infoflow/test/soot/jimple/infoflow/test/junit/JUnitTests.java index d0573aa26..4510a9cb7 100644 --- a/soot-infoflow/test/soot/jimple/infoflow/test/junit/JUnitTests.java +++ b/soot-infoflow/test/soot/jimple/infoflow/test/junit/JUnitTests.java @@ -18,6 +18,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import org.junit.Assume; @@ -26,14 +27,27 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import heros.solver.Pair; +import soot.Body; +import soot.Local; +import soot.Scene; +import soot.SootClass; +import soot.SootMethod; +import soot.Value; +import soot.ValueBox; import soot.jimple.infoflow.AbstractInfoflow; import soot.jimple.infoflow.BackwardsInfoflow; +import soot.jimple.infoflow.FlowDroidLocalSplitter.SplittedLocal; import soot.jimple.infoflow.IInfoflow; import soot.jimple.infoflow.Infoflow; import soot.jimple.infoflow.config.ConfigForTest; +import soot.jimple.infoflow.data.AccessPath; import soot.jimple.infoflow.results.InfoflowResults; +import soot.jimple.infoflow.results.ResultSinkInfo; +import soot.jimple.infoflow.results.ResultSourceInfo; import soot.jimple.infoflow.taintWrappers.EasyTaintWrapper; import soot.jimple.infoflow.test.base.AbstractJUnitTests; +import soot.util.MultiMap; /** * abstract super class of all test cases which handles initialization, keeps @@ -115,8 +129,12 @@ public void resetSootAndStream() throws IOException { } protected void checkInfoflow(IInfoflow infoflow, int resultCount) { + checkCodeForNoSplitLocals(); + //check that there are no splitted locals left if (infoflow.isResultAvailable()) { InfoflowResults map = infoflow.getResults(); + checkInfoflowResultsForNoSplitLocals(map); + assertEquals(resultCount, map.size()); assertTrue(map.containsSinkMethod(sink) || map.containsSinkMethod(sinkInt) || map.containsSinkMethod(sinkBoolean) || map.containsSinkMethod(sinkDouble)); @@ -135,6 +153,55 @@ protected void checkInfoflow(IInfoflow infoflow, int resultCount) { } } + private void checkCodeForNoSplitLocals() { + for (SootClass sc : Scene.v().getClasses()) { + for (SootMethod m : sc.getMethods()) { + if (m.hasActiveBody()) { + Body b = m.getActiveBody(); + for (Local l : b.getLocals()) { + if (l instanceof SplittedLocal) { + fail(String.format("Found split local in %s: %s", m.getSignature(), l.getName())); + } + } + + Iterator it = b.getUseAndDefBoxesIterator(); + while (it.hasNext()) { + ValueBox vb = it.next(); + Value v = vb.getValue(); + if (v instanceof SplittedLocal) { + fail(String.format("Found split local in %s: %s", m.getSignature(), v)); + } + } + } + } + } + } + + private void checkInfoflowResultsForNoSplitLocals(InfoflowResults map) { + MultiMap res = map.getResults(); + if (res != null) { + for (Pair pair : res) { + ResultSinkInfo sink = pair.getO1(); + ResultSourceInfo source = pair.getO2(); + checkAccessPathForSplitLocals(sink.getAccessPath()); + checkAccessPathForSplitLocals(source.getAccessPath()); + AccessPath[] app = source.getPathAccessPaths(); + if (app != null) { + for (AccessPath i : app) { + checkAccessPathForSplitLocals(i); + } + } + } + } + } + + private void checkAccessPathForSplitLocals(AccessPath ap) { + Local l = ap.getPlainValue(); + if (l instanceof SplittedLocal) { + fail("Split local found in " + ap); + } + } + protected void negativeCheckInfoflow(IInfoflow infoflow) { // If the result is available, it must be empty. Otherwise, it is // implicitly ok since we don't expect to find anything anyway. @@ -166,7 +233,7 @@ protected IInfoflow initInfoflow(boolean useTaintWrapper) { } } -// result.getConfig().setLogSourcesAndSinks(true); + // result.getConfig().setLogSourcesAndSinks(true); return result; } From 0cafb8e28e6bc77e9e2ec25b8a2798ac4bd77268 Mon Sep 17 00:00:00 2001 From: Marc Miltenberger Date: Mon, 26 Jan 2026 15:11:58 +0100 Subject: [PATCH 2/8] Do not load everything --- .../jimple/infoflow/AbstractInfoflow.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java b/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java index ccc643777..c57ecc75f 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java +++ b/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java @@ -16,10 +16,14 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.Iterators; +import com.google.common.collect.Streams; + import heros.solver.Pair; import soot.ArrayType; import soot.Body; @@ -527,7 +531,11 @@ protected void constructCallgraph() { if (config.getCallgraphAlgorithm() != CallgraphAlgorithm.OnDemand && !Scene.v().hasCallGraph()) { if (config.getAliasingAlgorithm() == AliasingAlgorithm.PtsBased) { //we need to split here already for the PTS to work correctly - splitAllBodies(); + Iterator allMethods = Iterators + .concat(Streams.stream(Scene.v().getApplicationClasses().snapshotIterator()).map(a -> { + return a.getMethods().iterator(); + }).collect(Collectors.toList()).iterator()); + splitAllBodies(allMethods); } PackManager.v().getPack("wjpp").apply(); @@ -920,7 +928,7 @@ protected void runAnalysis(final ISourceSinkManager sourcesSinks, final Set it) { FlowDroidLocalSplitter splitter = FlowDroidLocalSplitter.v(); - for (SootClass sc : new ArrayList<>(Scene.v().getApplicationClasses())) { - for (SootMethod m : new ArrayList<>(sc.getMethods())) { - if (m.isConcrete()) { - splitter.transform(m.retrieveActiveBody()); - } + while (it.hasNext()) { + MethodOrMethodContext mc = it.next(); + SootMethod m = mc.method(); + if (m.isConcrete()) { + splitter.transform(m.retrieveActiveBody()); } } } From e97a24417bff043ce9005b52a24ec18d71aee68f Mon Sep 17 00:00:00 2001 From: Marc Miltenberger Date: Mon, 26 Jan 2026 15:32:32 +0100 Subject: [PATCH 3/8] Split not all methods --- .../jimple/infoflow/AbstractInfoflow.java | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java b/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java index c57ecc75f..132a49aad 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java +++ b/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java @@ -16,14 +16,10 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; -import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.Iterators; -import com.google.common.collect.Streams; - import heros.solver.Pair; import soot.ArrayType; import soot.Body; @@ -501,10 +497,11 @@ protected void constructCallgraph() { // Allow the ICC manager to change the Soot Scene before we continue if (ipcManager != null) ipcManager.updateJimpleForICC(); + if (config.getAliasingAlgorithm() == AliasingAlgorithm.PtsBased) { + + } - // We might need to patch invokedynamic instructions - if (config.isPatchInvokeDynamicInstructions()) - patchDynamicInvokeInstructions(); + patchCode(); // Run the preprocessors for (PreAnalysisHandler tr : preProcessors) @@ -529,15 +526,6 @@ protected void constructCallgraph() { // 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()) { - if (config.getAliasingAlgorithm() == AliasingAlgorithm.PtsBased) { - //we need to split here already for the PTS to work correctly - Iterator allMethods = Iterators - .concat(Streams.stream(Scene.v().getApplicationClasses().snapshotIterator()).map(a -> { - return a.getMethods().iterator(); - }).collect(Collectors.toList()).iterator()); - splitAllBodies(allMethods); - } - PackManager.v().getPack("wjpp").apply(); PackManager.v().getPack("cg").apply(); } @@ -555,20 +543,20 @@ protected void constructCallgraph() { } /** - * Re-writes dynamic invocation instructions into traditional invcations + * Inserts patch-code logic */ - private void patchDynamicInvokeInstructions() { + private void patchCode() { for (SootClass sc : Scene.v().getClasses()) { for (SootMethod sm : sc.getMethods()) { if (sm.hasActiveBody()) { Body body = sm.getActiveBody(); - patchDynamicInvokeInstructions(body); + patchCode(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); + patchCode(b); } }); @@ -577,6 +565,13 @@ protected void onMethodSourceLoaded(SootMethod m, Body b) { } } + private void patchCode(Body body) { + if (config.isPatchInvokeDynamicInstructions()) { + patchDynamicInvokeInstructions(body); + } + FlowDroidLocalSplitter.v().transform(body); + } + /** * Patches the dynamic invocation instructions in the given method body * From b7e66101ab3c69af756c5c3bb1d0340f924791f2 Mon Sep 17 00:00:00 2001 From: Marc Miltenberger Date: Mon, 26 Jan 2026 18:27:58 +0100 Subject: [PATCH 4/8] Fix summary infoflow --- .../generator/SummaryInfoflow.java | 22 ++++++++++++++++++- .../jimple/infoflow/AbstractInfoflow.java | 13 +++++++---- .../src/soot/jimple/infoflow/SplittedTag.java | 19 ++++++++++++++++ 3 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 soot-infoflow/src/soot/jimple/infoflow/SplittedTag.java diff --git a/soot-infoflow-summaries/src/soot/jimple/infoflow/methodSummary/generator/SummaryInfoflow.java b/soot-infoflow-summaries/src/soot/jimple/infoflow/methodSummary/generator/SummaryInfoflow.java index e3b4c16e6..f9025ccb5 100644 --- a/soot-infoflow-summaries/src/soot/jimple/infoflow/methodSummary/generator/SummaryInfoflow.java +++ b/soot-infoflow-summaries/src/soot/jimple/infoflow/methodSummary/generator/SummaryInfoflow.java @@ -2,6 +2,8 @@ import java.util.Collection; +import soot.Local; +import soot.jimple.infoflow.FlowDroidLocalSplitter; import soot.jimple.infoflow.Infoflow; import soot.jimple.infoflow.InfoflowManager; import soot.jimple.infoflow.solver.IInfoflowSolver; @@ -35,10 +37,28 @@ public InfoflowManager getManager() { @Override protected void onTaintPropagationCompleted(IInfoflowSolver forwardSolver, IInfoflowSolver aliasSolver, - IInfoflowSolver backwardSolver, IInfoflowSolver backwardAliasSolver) { + IInfoflowSolver backwardSolver, IInfoflowSolver backwardAliasSolver) { cachedManager = this.manager; } + @Override + protected void unsplitAllBodies() { + //Since we are interested in abstractions, we must not unsplit. + //This is fine for our use case + } + + @Override + protected FlowDroidLocalSplitter getLocalSplitter() { + return new FlowDroidLocalSplitter() { + + @Override + protected Local createClonedLocal(Local oldLocal) { + //We want "normal" locals since we take deep looks into abstractions + return (Local) oldLocal.clone(); + } + }; + } + @Override protected void initializeSoot(String appPath, String libPath, Collection classes) { this.libPath = libPath; diff --git a/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java b/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java index 132a49aad..09f2dba07 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java +++ b/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java @@ -569,7 +569,11 @@ private void patchCode(Body body) { if (config.isPatchInvokeDynamicInstructions()) { patchDynamicInvokeInstructions(body); } - FlowDroidLocalSplitter.v().transform(body); + getLocalSplitter().transform(body); + } + + protected FlowDroidLocalSplitter getLocalSplitter() { + return FlowDroidLocalSplitter.v(); } /** @@ -956,7 +960,7 @@ protected void runAnalysis(final ISourceSinkManager sourcesSinks, final Set it) { - FlowDroidLocalSplitter splitter = FlowDroidLocalSplitter.v(); + FlowDroidLocalSplitter splitter = getLocalSplitter(); while (it.hasNext()) { MethodOrMethodContext mc = it.next(); SootMethod m = mc.method(); - if (m.isConcrete()) { + if (m.isConcrete() && m.getTag(SplittedTag.NAME) == null) { + m.addTag(SplittedTag.v()); splitter.transform(m.retrieveActiveBody()); } } diff --git a/soot-infoflow/src/soot/jimple/infoflow/SplittedTag.java b/soot-infoflow/src/soot/jimple/infoflow/SplittedTag.java new file mode 100644 index 000000000..084946a38 --- /dev/null +++ b/soot-infoflow/src/soot/jimple/infoflow/SplittedTag.java @@ -0,0 +1,19 @@ +package soot.jimple.infoflow; + +import soot.tagkit.Tag; + +public class SplittedTag implements Tag { + + public static final String NAME = "Splitted"; + private static final Tag INSTANCE = new SplittedTag(); + + @Override + public String getName() { + return NAME; + } + + public static Tag v() { + return INSTANCE; + } + +} From 2c51f0f8a8634d01016d5d4c0f086ccd2050d780 Mon Sep 17 00:00:00 2001 From: Marc Miltenberger Date: Tue, 27 Jan 2026 14:00:20 +0100 Subject: [PATCH 5/8] Prevent NPE --- .../manager/BaseSourceSinkManager.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/soot-infoflow/src/soot/jimple/infoflow/sourcesSinks/manager/BaseSourceSinkManager.java b/soot-infoflow/src/soot/jimple/infoflow/sourcesSinks/manager/BaseSourceSinkManager.java index 865c8fd38..df347680b 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/sourcesSinks/manager/BaseSourceSinkManager.java +++ b/soot-infoflow/src/soot/jimple/infoflow/sourcesSinks/manager/BaseSourceSinkManager.java @@ -294,13 +294,15 @@ protected Collection getSinkDefinitions(Stmt sCallSite, I final String subSig = callee.getSubSignature(); - // Check whether we have any of the interfaces on the list - for (SootClass i : parentClassesAndInterfaces - .getUnchecked(sCallSite.getInvokeExpr().getMethod().getDeclaringClass())) { - if (i.declaresMethod(subSig)) { - Collection def = this.sinkMethods.get(i.getMethod(subSig)); - if (def.size() > 0) - return def; + SootClass decl = sCallSite.getInvokeExpr().getMethod().getDeclaringClass(); + if (decl != null) { + // Check whether we have any of the interfaces on the list + for (SootClass i : parentClassesAndInterfaces.getUnchecked(decl)) { + if (i.declaresMethod(subSig)) { + Collection def = this.sinkMethods.get(i.getMethod(subSig)); + if (def.size() > 0) + return def; + } } } From e18ed3cb9eb9ff586df42d960e3eae2114273d72 Mon Sep 17 00:00:00 2001 From: Marc Miltenberger Date: Wed, 28 Jan 2026 10:10:02 +0100 Subject: [PATCH 6/8] Log out weird cases --- .../infoflow/android/source/AndroidSourceSinkManager.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/source/AndroidSourceSinkManager.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/source/AndroidSourceSinkManager.java index 536c1fadd..4ecbe3282 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/source/AndroidSourceSinkManager.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/source/AndroidSourceSinkManager.java @@ -440,6 +440,12 @@ protected Collection getSinkDefinitions(Stmt sCallSite, I final String subSig = callee.getSubSignature(); final SootClass sc = callee.getDeclaringClass(); + if (sc == null) { + logger.warn( + String.format("%s was not declared; was called at %s", callee.getSubSignature(), sCallSite)); + return sinkDefs; + } + // Do not consider ICC methods as sinks if only the base object is // tainted boolean isParamTainted = false; From 1c783761a04f0a67a4771ef4d60f38b872903e5b Mon Sep 17 00:00:00 2001 From: Marc Miltenberger Date: Thu, 29 Jan 2026 09:10:53 +0100 Subject: [PATCH 7/8] Remove unused locals --- .../infoflow/FlowDroidLocalSplitter.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/soot-infoflow/src/soot/jimple/infoflow/FlowDroidLocalSplitter.java b/soot-infoflow/src/soot/jimple/infoflow/FlowDroidLocalSplitter.java index 94be7c879..d78ddeb1a 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/FlowDroidLocalSplitter.java +++ b/soot-infoflow/src/soot/jimple/infoflow/FlowDroidLocalSplitter.java @@ -1,14 +1,19 @@ package soot.jimple.infoflow; +import java.util.Map; + +import soot.Body; import soot.Local; import soot.Singletons.Global; import soot.jimple.internal.JimpleLocal; import soot.toolkits.scalar.LocalSplitter; +import soot.toolkits.scalar.UnusedLocalEliminator; /** - * With more recent soot versions, locals are reused more often. - * This can cause problems in FlowDroid (e.g. the overwriteParameter test case). - * The simple solution: We split these locals beforehand + * With more recent soot versions, locals are reused more often. This can cause + * problems in FlowDroid (e.g. the overwriteParameter test case). The simple + * solution: We split these locals beforehand + * * @author Marc Miltenberger */ public class FlowDroidLocalSplitter extends LocalSplitter { @@ -19,7 +24,7 @@ public static class SplittedLocal extends JimpleLocal { public SplittedLocal(JimpleLocal oldLocal) { super(null, oldLocal.getType()); - //do not intern the name again + // do not intern the name again setName(oldLocal.getName()); if (oldLocal.isUserDefinedLocal()) { setUserDefinedLocal(); @@ -43,7 +48,7 @@ public FlowDroidLocalSplitter() { @Override protected String getNewName(String name, int count) { - //Reuse the old name + // Reuse the old name return name; } @@ -56,4 +61,10 @@ public static FlowDroidLocalSplitter v() { return new FlowDroidLocalSplitter(); } + @Override + protected void internalTransform(Body body, String phaseName, Map options) { + super.internalTransform(body, phaseName, options); + UnusedLocalEliminator.v().transform(body); + } + } From 79e3450e135af360cef4c9bf7b570bc0f278c809 Mon Sep 17 00:00:00 2001 From: Marc Miltenberger Date: Thu, 29 Jan 2026 13:38:21 +0100 Subject: [PATCH 8/8] Allow subclasses to get information about the completion rate --- .../infoflow/data/pathBuilders/BatchPathBuilder.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/soot-infoflow/src/soot/jimple/infoflow/data/pathBuilders/BatchPathBuilder.java b/soot-infoflow/src/soot/jimple/infoflow/data/pathBuilders/BatchPathBuilder.java index 538dac8eb..a0cbb74c9 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/data/pathBuilders/BatchPathBuilder.java +++ b/soot-infoflow/src/soot/jimple/infoflow/data/pathBuilders/BatchPathBuilder.java @@ -38,6 +38,7 @@ public void computeTaintPaths(Set res) { int batchId = 1; long startTime = System.nanoTime(); long totalTime = manager.getConfig().getPathConfiguration().getPathReconstructionTotalTime(); + int completed = 0; while (resIt.hasNext()) { // checking if the execution time exceeds the configured totalTime and logging @@ -86,7 +87,8 @@ public void computeTaintPaths(Set res) { resultExecutor.reset(); } logger.info("Single batch has used " + (System.nanoTime() - beforeBatch) / 1E9 + " seconds"); - + completed += batch.size(); + reportCompletion(completed, res.size()); // If the analysis failed due to an OOM, it doesn't make sense to proceed with // the next batch and get into yet another OOM ISolverTerminationReason currentReason = innerBuilder.getTerminationReason(); @@ -106,6 +108,10 @@ public void computeTaintPaths(Set res) { } } + protected void reportCompletion(int completed, int totalTasks) { + + } + @Override public InfoflowResults getResults() { return innerBuilder.getResults();