From bb393b9ed11ff62b337f9610b049c59e255eb998 Mon Sep 17 00:00:00 2001 From: Valentyn Sobol <8640896+Saloed@users.noreply.github.com> Date: Fri, 19 Jun 2026 20:37:48 +0300 Subject: [PATCH 1/6] Apply heap aliases --- .../approximations/stdlib/Iterable.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 core/opentaint-jvm-sast-dataflow/dataflow-approximations/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Iterable.java diff --git a/core/opentaint-jvm-sast-dataflow/dataflow-approximations/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Iterable.java b/core/opentaint-jvm-sast-dataflow/dataflow-approximations/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Iterable.java new file mode 100644 index 000000000..fdb9a3544 --- /dev/null +++ b/core/opentaint-jvm-sast-dataflow/dataflow-approximations/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Iterable.java @@ -0,0 +1,17 @@ +package org.opentaint.jvm.dataflow.approximations.stdlib; + +import org.opentaint.ir.approximation.annotation.Approximate; + +import java.util.Iterator; +import java.util.function.Consumer; + +@Approximate(java.lang.Iterable.class) +public class Iterable { + public void forEach(Consumer action) { + java.lang.Iterable t = (java.lang.Iterable) (Object) this; + Iterator it = t.iterator(); + if (it.hasNext()) { + action.accept(it.next()); + } + } +} From 0279b28eb5f164dde43871d74786d8ac56681e6f Mon Sep 17 00:00:00 2001 From: Daniil Logunov <43185213+DanielELog@users.noreply.github.com> Date: Fri, 19 Jun 2026 21:42:34 +0300 Subject: [PATCH 2/6] feat(analyzer): Add test requiring alias computation for fields (#209) --- .../main/java/issues/i98/User_i98_deep.java | 85 +++++++++++++++++++ .../src/main/java/issues/issue98_deep.java | 38 +++++++++ .../src/main/resources/issues/issue98.yaml | 11 +++ .../org/opentaint/semgrep/IssuesTest.kt | 5 ++ 4 files changed, 139 insertions(+) create mode 100644 core/opentaint-java-querylang/samples/src/main/java/issues/i98/User_i98_deep.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/issues/issue98_deep.java create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml diff --git a/core/opentaint-java-querylang/samples/src/main/java/issues/i98/User_i98_deep.java b/core/opentaint-java-querylang/samples/src/main/java/issues/i98/User_i98_deep.java new file mode 100644 index 000000000..feb61cbd9 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/issues/i98/User_i98_deep.java @@ -0,0 +1,85 @@ +package issues.i98; + +public class User_i98_deep { + private String badString() { + return "42"; + } + + private class Caller { + void call(Depth2 r, String b) { + r.depth1.data = b; + } + + void call(Depth3 r, String b) { + r.depth2.depth1.data = b; + } + + void call(Depth4 r, String b) { + r.depth3.depth2.depth1.data = b; + } + } + + private class Depth1 { + String data = ""; + } + + private class Depth2 { + Depth1 depth1 = new Depth1(); + } + + private class Depth3 { + Depth2 depth2 = new Depth2(); + } + + private class Depth4 { + Depth3 depth3 = new Depth3(); + } + + public String badUser() { + Depth2 d2 = new Depth2(); + Depth1 d = d2.depth1; + Caller k = new Caller(); + k.call(d2, badString()); + return d.data; + } + + public String badUserDepth4() { + Depth4 d4 = new Depth4(); + Depth3 d3 = d4.depth3; + Caller k = new Caller(); + k.call(d4, badString()); + return d3.depth2.depth1.data; + } + + public String badUserDepth3() { + Depth4 d4 = new Depth4(); + Depth2 d2 = d4.depth3.depth2; + Caller k = new Caller(); + k.call(d4, badString()); + return d2.depth1.data; + } + + public String badUserDepth2() { + Depth4 d4 = new Depth4(); + Depth1 d1 = d4.depth3.depth2.depth1; + Caller k = new Caller(); + k.call(d4, badString()); + return d1.data; + } + + public String badUserDepth4Call2() { + Depth4 d4 = new Depth4(); + Depth3 d3 = d4.depth3; + Caller k = new Caller(); + k.call(d4.depth3.depth2, badString()); + return d3.depth2.depth1.data; + } + + public String badUserDepth4Call3() { + Depth4 d4 = new Depth4(); + Depth2 d2 = d4.depth3.depth2; + Caller k = new Caller(); + k.call(d4.depth3, badString()); + return d2.depth1.data; + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/issues/issue98_deep.java b/core/opentaint-java-querylang/samples/src/main/java/issues/issue98_deep.java new file mode 100644 index 000000000..f01a148f6 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/issues/issue98_deep.java @@ -0,0 +1,38 @@ +package issues; + +import base.RuleSample; +import base.RuleSet; +import issues.i98.User_i98_deep; + +@RuleSet("issues/issue98.yaml") +public abstract class issue98_deep implements RuleSample { + static class PositiveTaint extends issue98_deep { + @Override + public void entrypoint() { (new User_i98_deep()).badUser(); } + } + + static class PositiveDepth4 extends issue98_deep { + @Override + public void entrypoint() { (new User_i98_deep()).badUserDepth4(); } + } + + static class PositiveDepth3 extends issue98_deep { + @Override + public void entrypoint() { (new User_i98_deep()).badUserDepth3(); } + } + + static class PositiveDepth2 extends issue98_deep { + @Override + public void entrypoint() { (new User_i98_deep()).badUserDepth2(); } + } + + static class PositiveDepth4Call2 extends issue98_deep { + @Override + public void entrypoint() { (new User_i98_deep()).badUserDepth4Call2(); } + } + + static class PositiveDepth4Call3 extends issue98_deep { + @Override + public void entrypoint() { (new User_i98_deep()).badUserDepth4Call3(); } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml b/core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml new file mode 100644 index 000000000..fc0e92806 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml @@ -0,0 +1,11 @@ +rules: + - id: i97 + languages: + - java + severity: ERROR + message: badMethod + patterns: + - pattern: | + $A = badString(); + ... + return $A; diff --git a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt index 9bcce3a57..440c1cbf2 100644 --- a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt +++ b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt @@ -18,6 +18,7 @@ import issues.issue96 import issues.issue97 import issues.issueChain import issues.issueChainSplitBuilder +import issues.issue98_deep import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.TestInstance @@ -113,6 +114,10 @@ class IssuesTest : SampleBasedTest() { @Test fun `issue chain-pattern split builder`() = runTest(EXPECT_STATE_VAR) + @Test + @Disabled // todo: implement alias calculation for field chains + fun `issue 98 deep`() = runTest() + @AfterAll fun close() { closeRunner() From 22712a7669d8786882c0f2ba37c2a5650928120d Mon Sep 17 00:00:00 2001 From: Valentyn Sobol <8640896+Saloed@users.noreply.github.com> Date: Fri, 19 Jun 2026 22:01:37 +0300 Subject: [PATCH 3/6] Test --- .../main/java/issues/i98/User_i98_deep.java | 28 ++++++++----------- .../src/main/java/issues/issue98_deep.java | 19 +++++++++---- .../src/main/resources/issues/issue98.yaml | 2 +- .../org/opentaint/semgrep/IssuesTest.kt | 2 +- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/core/opentaint-java-querylang/samples/src/main/java/issues/i98/User_i98_deep.java b/core/opentaint-java-querylang/samples/src/main/java/issues/i98/User_i98_deep.java index feb61cbd9..58b149f23 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/issues/i98/User_i98_deep.java +++ b/core/opentaint-java-querylang/samples/src/main/java/issues/i98/User_i98_deep.java @@ -1,10 +1,6 @@ package issues.i98; public class User_i98_deep { - private String badString() { - return "42"; - } - private class Caller { void call(Depth2 r, String b) { r.depth1.data = b; @@ -35,51 +31,51 @@ private class Depth4 { Depth3 depth3 = new Depth3(); } - public String badUser() { + public String badUser(String badString) { Depth2 d2 = new Depth2(); Depth1 d = d2.depth1; Caller k = new Caller(); - k.call(d2, badString()); + k.call(d2, badString); return d.data; } - public String badUserDepth4() { + public String badUserDepth4(String badString) { Depth4 d4 = new Depth4(); Depth3 d3 = d4.depth3; Caller k = new Caller(); - k.call(d4, badString()); + k.call(d4, badString); return d3.depth2.depth1.data; } - public String badUserDepth3() { + public String badUserDepth3(String badString) { Depth4 d4 = new Depth4(); Depth2 d2 = d4.depth3.depth2; Caller k = new Caller(); - k.call(d4, badString()); + k.call(d4, badString); return d2.depth1.data; } - public String badUserDepth2() { + public String badUserDepth2(String badString) { Depth4 d4 = new Depth4(); Depth1 d1 = d4.depth3.depth2.depth1; Caller k = new Caller(); - k.call(d4, badString()); + k.call(d4, badString); return d1.data; } - public String badUserDepth4Call2() { + public String badUserDepth4Call2(String badString) { Depth4 d4 = new Depth4(); Depth3 d3 = d4.depth3; Caller k = new Caller(); - k.call(d4.depth3.depth2, badString()); + k.call(d4.depth3.depth2, badString); return d3.depth2.depth1.data; } - public String badUserDepth4Call3() { + public String badUserDepth4Call3(String badString) { Depth4 d4 = new Depth4(); Depth2 d2 = d4.depth3.depth2; Caller k = new Caller(); - k.call(d4.depth3, badString()); + k.call(d4.depth3, badString); return d2.depth1.data; } } diff --git a/core/opentaint-java-querylang/samples/src/main/java/issues/issue98_deep.java b/core/opentaint-java-querylang/samples/src/main/java/issues/issue98_deep.java index f01a148f6..35b96052d 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/issues/issue98_deep.java +++ b/core/opentaint-java-querylang/samples/src/main/java/issues/issue98_deep.java @@ -6,33 +6,40 @@ @RuleSet("issues/issue98.yaml") public abstract class issue98_deep implements RuleSample { + static String badString() { + return "42"; + } + + static void sink(String data) { + } + static class PositiveTaint extends issue98_deep { @Override - public void entrypoint() { (new User_i98_deep()).badUser(); } + public void entrypoint() { sink((new User_i98_deep()).badUser(badString())); } } static class PositiveDepth4 extends issue98_deep { @Override - public void entrypoint() { (new User_i98_deep()).badUserDepth4(); } + public void entrypoint() { sink((new User_i98_deep()).badUserDepth4(badString())); } } static class PositiveDepth3 extends issue98_deep { @Override - public void entrypoint() { (new User_i98_deep()).badUserDepth3(); } + public void entrypoint() { sink((new User_i98_deep()).badUserDepth3(badString())); } } static class PositiveDepth2 extends issue98_deep { @Override - public void entrypoint() { (new User_i98_deep()).badUserDepth2(); } + public void entrypoint() { sink((new User_i98_deep()).badUserDepth2(badString())); } } static class PositiveDepth4Call2 extends issue98_deep { @Override - public void entrypoint() { (new User_i98_deep()).badUserDepth4Call2(); } + public void entrypoint() { sink((new User_i98_deep()).badUserDepth4Call2(badString())); } } static class PositiveDepth4Call3 extends issue98_deep { @Override - public void entrypoint() { (new User_i98_deep()).badUserDepth4Call3(); } + public void entrypoint() { sink((new User_i98_deep()).badUserDepth4Call3(badString())); } } } diff --git a/core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml b/core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml index fc0e92806..f2dfc648c 100644 --- a/core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml +++ b/core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml @@ -8,4 +8,4 @@ rules: - pattern: | $A = badString(); ... - return $A; + sink($A); diff --git a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt index 440c1cbf2..ba570158a 100644 --- a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt +++ b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt @@ -115,7 +115,7 @@ class IssuesTest : SampleBasedTest() { fun `issue chain-pattern split builder`() = runTest(EXPECT_STATE_VAR) @Test - @Disabled // todo: implement alias calculation for field chains + // todo: trace resolution fun `issue 98 deep`() = runTest() @AfterAll From d0a5eade1903977ecdb5495cffd9a81d4b2b6fed Mon Sep 17 00:00:00 2001 From: Valentyn Sobol <8640896+Saloed@users.noreply.github.com> Date: Fri, 19 Jun 2026 22:12:11 +0300 Subject: [PATCH 4/6] Fix trace resolver --- .../jvm/ap/ifds/analysis/JIRAliasUtil.kt | 97 ++++++++++++++----- .../analysis/JIRMethodCallFlowFunction.kt | 2 +- .../ifds/trace/JIRMethodCallPrecondition.kt | 6 ++ 3 files changed, 82 insertions(+), 23 deletions(-) diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt index 35de0c4e1..222b26eae 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt @@ -5,40 +5,73 @@ import org.opentaint.dataflow.ap.ifds.Accessor import org.opentaint.dataflow.ap.ifds.ClassStaticAccessor import org.opentaint.dataflow.ap.ifds.ElementAccessor import org.opentaint.dataflow.ap.ifds.FieldAccessor +import org.opentaint.dataflow.ap.ifds.access.FactAp import org.opentaint.dataflow.ap.ifds.access.FinalFactAp import org.opentaint.dataflow.ap.ifds.access.InitialFactAp -import org.opentaint.dataflow.ap.ifds.analysis.alias.applyAlias -import org.opentaint.dataflow.ap.ifds.analysis.alias.forEachAliasAtStatement -import org.opentaint.dataflow.ap.ifds.analysis.alias.forEachAliasAtStatementAmongBases -import org.opentaint.dataflow.ap.ifds.analysis.alias.forEachHeapAliasBeforeStatement import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAccessor import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasApInfo -import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasInfo import org.opentaint.dataflow.jvm.ap.ifds.MethodFlowFunctionUtils import org.opentaint.ir.api.jvm.cfg.JIRInst import org.opentaint.ir.api.jvm.cfg.locals fun JIRLocalAliasAnalysis.forEachAliasAtStatement(statement: JIRInst, fact: FinalFactAp, body: (FinalFactAp) -> Unit) = - forEachAliasAtStatement(statement, fact, AliasInfo::relevantApInfo, AliasAccessor::apAccessor, body) + forEachAliasAtStatement(statement, fact) { fact, alias -> applyAlias(fact, alias, body) } fun JIRLocalAliasAnalysis.forEachAliasAtStatement(statement: JIRInst, fact: InitialFactAp, body: (InitialFactAp) -> Unit) = - forEachAliasAtStatement(statement, fact, AliasInfo::relevantApInfo, AliasAccessor::apAccessor, body) + forEachAliasAtStatement(statement, fact) { fact, alias -> applyAlias(fact, alias, body) } -fun JIRLocalAliasAnalysis.forEachAliasAfterCallStatement(statement: JIRInst, fact: FinalFactAp, body: (FinalFactAp) -> Unit) { +private inline fun JIRLocalAliasAnalysis.forEachAliasAtStatement( + statement: JIRInst, + fact: F, + applyAlias: (F, AliasApInfo) -> Unit +) { val base = fact.base as? AccessPathBase.LocalVar ?: return - val aliasesBefore = findAlias(base, statement) ?: return - val aliasesAfter = findAliasAfterStatement(base, statement)?.toSet() ?: return - val aliasesPersistedThroughCall = aliasesBefore.filter { it in aliasesAfter } - - aliasesPersistedThroughCall - .filterIsInstance() + val aliases = findAlias(base, statement) ?: return + aliases.filterIsInstance() .filterNot { alias -> alias.base is AccessPathBase.Constant } - .forEach { alias -> applyAlias(fact, alias, AliasAccessor::apAccessor, body) } + .forEach { alias -> applyAlias(fact, alias) } +} + +fun JIRLocalAliasAnalysis.forEachAliasBeforeCallStatement( + statement: JIRInst, + fact: FinalFactAp, + body: (FinalFactAp) -> Unit +) { + val base = fact.base as? AccessPathBase.LocalVar ?: return + forEachHeapAlias(base, statement, fact, { f -> + val next = mutableListOf>() + val accessors = f.getStartAccessors() + for (accessor in accessors) { + val aa = accessor.aliasAccessor() ?: continue + val nexFact = f.readAccessor(accessor) ?: continue + next.add(aa to nexFact) + } + next + }) { alias, f -> + if (alias is AliasApInfo && alias.base !is AccessPathBase.Constant) { + applyAlias(f, alias, body) + } + } +} + +private fun applyAlias(fact: FinalFactAp, alias: AliasApInfo, body: (FinalFactAp) -> Unit) { + val result = alias.accessors.foldRight(fact.rebase(alias.base)) { accessor, f -> + val apAccessor = accessor.apAccessor() + f.prependAccessor(apAccessor) + } + + body(result) } -fun JIRLocalAliasAnalysis.forEachHeapAliasBeforeStatement(statement: JIRInst, fact: FinalFactAp, body: (FinalFactAp) -> Unit) = - forEachHeapAliasBeforeStatement(statement, fact, Accessor::aliasAccessor, AliasInfo::relevantApInfo, AliasAccessor::apAccessor, body) +private fun applyAlias(fact: InitialFactAp, alias: AliasApInfo, body: (InitialFactAp) -> Unit) { + val result = alias.accessors.foldRight(fact.rebase(alias.base)) { accessor, f -> + val apAccessor = accessor.apAccessor() + f.prependAccessor(apAccessor) + } + + body(result) +} fun JIRLocalAliasAnalysis.forEachPossibleAliasAtStatement( statement: JIRInst, @@ -57,12 +90,32 @@ fun JIRLocalAliasAnalysis.forEachAliasAtStatementAmongBases( fact: InitialFactAp, bases: List, body: (InitialFactAp) -> Unit -) = forEachAliasAtStatementAmongBases( - statement, fact, bases, AliasInfo::relevantApInfo, AliasAccessor::apAccessor, body -) +) { + bases.forEach { base -> + val aliases = findAlias(base, statement) ?: return@forEach + aliases.filterIsInstance() + .filterNot { alias -> alias.base is AccessPathBase.Constant } + .forEach { alias -> unapplyAlias(fact, base, alias, body) } + } +} + +private fun unapplyAlias( + fact: InitialFactAp, + newBase: AccessPathBase.LocalVar, + alias: AliasApInfo, + body: (InitialFactAp) -> Unit +) { + if (alias.base != fact.base) { + return + } -private fun AliasInfo.relevantApInfo(): AliasApInfo? = - (this as? AliasApInfo)?.takeIf { it.base !is AccessPathBase.Constant } + val result = alias.accessors.fold(fact.rebase(newBase)) { f, accessor -> + val apAccessor = accessor.apAccessor() + f.readAccessor(apAccessor) ?: return + } + + body(result) +} fun AliasAccessor.apAccessor(): Accessor = when (this) { is AliasAccessor.Array -> ElementAccessor diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt index 28743e77e..6be8e7adc 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt @@ -381,7 +381,7 @@ class JIRMethodCallFlowFunction( return } - analysisContext.aliasAnalysis?.forEachAliasAfterCallStatement(statement, this) { aliased -> + analysisContext.aliasAnalysis?.forEachAliasBeforeCallStatement(statement, this) { aliased -> body(aliased) } } diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/trace/JIRMethodCallPrecondition.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/trace/JIRMethodCallPrecondition.kt index 70fb78cf4..08642618b 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/trace/JIRMethodCallPrecondition.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/trace/JIRMethodCallPrecondition.kt @@ -20,6 +20,7 @@ import org.opentaint.dataflow.jvm.ap.ifds.JIRMethodCallFactMapper.factIsRelevant import org.opentaint.dataflow.jvm.ap.ifds.MethodFlowFunctionUtils import org.opentaint.dataflow.jvm.ap.ifds.TaintConfigUtils.accept import org.opentaint.dataflow.jvm.ap.ifds.analysis.JIRMethodAnalysisContext +import org.opentaint.dataflow.jvm.ap.ifds.analysis.forEachAliasAtStatement import org.opentaint.dataflow.jvm.ap.ifds.analysis.forEachPossibleAliasAtStatement import org.opentaint.dataflow.jvm.ap.ifds.taint.TaintRulesProvider import org.opentaint.dataflow.jvm.ap.ifds.taint.resolveAp @@ -58,6 +59,11 @@ class JIRMethodCallPrecondition( preconditionForFact(aliasedFact)?.let { results += PreconditionFactsForInitialFact(aliasedFact, it) } } + // todo: do we need to explore all accessors? + analysisContext.aliasAnalysis?.forEachAliasAtStatement(statement, fact) { aliasedFact -> + preconditionForFact(aliasedFact)?.let { results += PreconditionFactsForInitialFact(aliasedFact, it) } + } + return results } From 0dd6eafb081781c5e2f493b1530674988f2fe6b0 Mon Sep 17 00:00:00 2001 From: Valentyn Sobol <8640896+Saloed@users.noreply.github.com> Date: Fri, 19 Jun 2026 23:25:39 +0300 Subject: [PATCH 5/6] Common local alias analysis --- .../jvm/ap/ifds/analysis/JIRAliasUtil.kt | 91 +++---------------- .../analysis/JIRMethodCallFlowFunction.kt | 2 +- .../analysis/JIRMethodCallSummaryHandler.kt | 2 +- 3 files changed, 15 insertions(+), 80 deletions(-) diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt index 222b26eae..753f92bb5 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt @@ -5,73 +5,28 @@ import org.opentaint.dataflow.ap.ifds.Accessor import org.opentaint.dataflow.ap.ifds.ClassStaticAccessor import org.opentaint.dataflow.ap.ifds.ElementAccessor import org.opentaint.dataflow.ap.ifds.FieldAccessor -import org.opentaint.dataflow.ap.ifds.access.FactAp import org.opentaint.dataflow.ap.ifds.access.FinalFactAp import org.opentaint.dataflow.ap.ifds.access.InitialFactAp +import org.opentaint.dataflow.ap.ifds.analysis.alias.forEachAliasAtStatement +import org.opentaint.dataflow.ap.ifds.analysis.alias.forEachAliasAtStatementAmongBases +import org.opentaint.dataflow.ap.ifds.analysis.alias.forEachHeapAliasBeforeStatement import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAccessor import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasApInfo +import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasInfo import org.opentaint.dataflow.jvm.ap.ifds.MethodFlowFunctionUtils import org.opentaint.ir.api.jvm.cfg.JIRInst import org.opentaint.ir.api.jvm.cfg.locals fun JIRLocalAliasAnalysis.forEachAliasAtStatement(statement: JIRInst, fact: FinalFactAp, body: (FinalFactAp) -> Unit) = - forEachAliasAtStatement(statement, fact) { fact, alias -> applyAlias(fact, alias, body) } + forEachAliasAtStatement(statement, fact, AliasInfo::relevantApInfo, AliasAccessor::apAccessor, body) fun JIRLocalAliasAnalysis.forEachAliasAtStatement(statement: JIRInst, fact: InitialFactAp, body: (InitialFactAp) -> Unit) = - forEachAliasAtStatement(statement, fact) { fact, alias -> applyAlias(fact, alias, body) } + forEachAliasAtStatement(statement, fact, AliasInfo::relevantApInfo, AliasAccessor::apAccessor, body) -private inline fun JIRLocalAliasAnalysis.forEachAliasAtStatement( - statement: JIRInst, - fact: F, - applyAlias: (F, AliasApInfo) -> Unit -) { - val base = fact.base as? AccessPathBase.LocalVar ?: return - val aliases = findAlias(base, statement) ?: return - aliases.filterIsInstance() - .filterNot { alias -> alias.base is AccessPathBase.Constant } - .forEach { alias -> applyAlias(fact, alias) } -} - -fun JIRLocalAliasAnalysis.forEachAliasBeforeCallStatement( - statement: JIRInst, - fact: FinalFactAp, - body: (FinalFactAp) -> Unit -) { - val base = fact.base as? AccessPathBase.LocalVar ?: return - forEachHeapAlias(base, statement, fact, { f -> - val next = mutableListOf>() - val accessors = f.getStartAccessors() - for (accessor in accessors) { - val aa = accessor.aliasAccessor() ?: continue - val nexFact = f.readAccessor(accessor) ?: continue - next.add(aa to nexFact) - } - next - }) { alias, f -> - if (alias is AliasApInfo && alias.base !is AccessPathBase.Constant) { - applyAlias(f, alias, body) - } - } -} - -private fun applyAlias(fact: FinalFactAp, alias: AliasApInfo, body: (FinalFactAp) -> Unit) { - val result = alias.accessors.foldRight(fact.rebase(alias.base)) { accessor, f -> - val apAccessor = accessor.apAccessor() - f.prependAccessor(apAccessor) - } - - body(result) -} -private fun applyAlias(fact: InitialFactAp, alias: AliasApInfo, body: (InitialFactAp) -> Unit) { - val result = alias.accessors.foldRight(fact.rebase(alias.base)) { accessor, f -> - val apAccessor = accessor.apAccessor() - f.prependAccessor(apAccessor) - } - - body(result) -} +fun JIRLocalAliasAnalysis.forEachHeapAliasBeforeStatement(statement: JIRInst, fact: FinalFactAp, body: (FinalFactAp) -> Unit) = + forEachHeapAliasBeforeStatement(statement, fact, Accessor::aliasAccessor, AliasInfo::relevantApInfo, AliasAccessor::apAccessor, body) fun JIRLocalAliasAnalysis.forEachPossibleAliasAtStatement( statement: JIRInst, @@ -90,32 +45,12 @@ fun JIRLocalAliasAnalysis.forEachAliasAtStatementAmongBases( fact: InitialFactAp, bases: List, body: (InitialFactAp) -> Unit -) { - bases.forEach { base -> - val aliases = findAlias(base, statement) ?: return@forEach - aliases.filterIsInstance() - .filterNot { alias -> alias.base is AccessPathBase.Constant } - .forEach { alias -> unapplyAlias(fact, base, alias, body) } - } -} - -private fun unapplyAlias( - fact: InitialFactAp, - newBase: AccessPathBase.LocalVar, - alias: AliasApInfo, - body: (InitialFactAp) -> Unit -) { - if (alias.base != fact.base) { - return - } +) = forEachAliasAtStatementAmongBases( + statement, fact, bases, AliasInfo::relevantApInfo, AliasAccessor::apAccessor, body +) - val result = alias.accessors.fold(fact.rebase(newBase)) { f, accessor -> - val apAccessor = accessor.apAccessor() - f.readAccessor(apAccessor) ?: return - } - - body(result) -} +private fun AliasInfo.relevantApInfo(): AliasApInfo? = + (this as? AliasApInfo)?.takeIf { it.base !is AccessPathBase.Constant } fun AliasAccessor.apAccessor(): Accessor = when (this) { is AliasAccessor.Array -> ElementAccessor diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt index 6be8e7adc..2c4c53633 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt @@ -381,7 +381,7 @@ class JIRMethodCallFlowFunction( return } - analysisContext.aliasAnalysis?.forEachAliasBeforeCallStatement(statement, this) { aliased -> + analysisContext.aliasAnalysis?.forEachHeapAliasBeforeStatement(statement, this) { aliased -> body(aliased) } } diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodCallSummaryHandler.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodCallSummaryHandler.kt index 865093a7b..de4d86d1e 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodCallSummaryHandler.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodCallSummaryHandler.kt @@ -90,7 +90,7 @@ class JIRMethodCallSummaryHandler( } private fun applyCallAliases(fact: FinalFactAp, body: (FinalFactAp) -> Unit) { - analysisContext.aliasAnalysis?.forEachAliasAfterCallStatement(statement, fact) { aliased -> + analysisContext.aliasAnalysis?.forEachHeapAliasBeforeStatement(statement, fact) { aliased -> body(aliased) } } From 51b4a2adade4b3e8a602f8045e4765be79e02efa Mon Sep 17 00:00:00 2001 From: Valentyn Sobol <8640896+Saloed@users.noreply.github.com> Date: Tue, 23 Jun 2026 22:25:15 +0300 Subject: [PATCH 6/6] minor --- .../src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt index ba570158a..b6266eb3c 100644 --- a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt +++ b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt @@ -115,7 +115,6 @@ class IssuesTest : SampleBasedTest() { fun `issue chain-pattern split builder`() = runTest(EXPECT_STATE_VAR) @Test - // todo: trace resolution fun `issue 98 deep`() = runTest() @AfterAll