From 9d56f063388f637aa8f8c489705c4a782143b47b Mon Sep 17 00:00:00 2001 From: Daniil Logunov <43185213+DanielELog@users.noreply.github.com> Date: Fri, 20 Mar 2026 14:33:17 +0300 Subject: [PATCH 01/12] WIP must alias --- .../jvm/ap/ifds/JIRLocalAliasAnalysis.kt | 19 +- .../jvm/ap/ifds/alias/DSUAliasAnalysis.kt | 198 +------- .../dataflow/jvm/ap/ifds/alias/DSUState.kt | 247 ++++++++++ .../ifds/alias/JIRIntraProcAliasAnalysis.kt | 3 +- .../ap/ifds/taint/JIRBasicAtomEvaluator.kt | 8 +- .../opentaint/dataflow/jvm/BasicTestUtils.kt | 91 ++++ .../ifds/alias/DSUAliasAnalysisStateTest.kt | 3 +- ...iasSampleTest.kt => MayAliasSampleTest.kt} | 166 +------ .../jvm/ap/ifds/alias/MustAliasSampleTest.kt | 457 ++++++++++++++++++ 9 files changed, 826 insertions(+), 366 deletions(-) create mode 100644 core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUState.kt rename core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/{AliasSampleTest.kt => MayAliasSampleTest.kt} (67%) create mode 100644 core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MustAliasSampleTest.kt diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt index 97b23c0ba..e150be7ee 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt @@ -3,6 +3,7 @@ package org.opentaint.dataflow.jvm.ap.ifds import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import org.opentaint.dataflow.ap.ifds.AccessPathBase import org.opentaint.dataflow.jvm.ap.ifds.alias.JIRIntraProcAliasAnalysis +import org.opentaint.dataflow.jvm.ap.ifds.alias.MergeType import org.opentaint.ir.api.common.cfg.CommonInst import org.opentaint.ir.api.jvm.cfg.JIRInst import org.opentaint.jvm.graph.JApplicationGraph @@ -23,7 +24,8 @@ class JIRLocalAliasAnalysis( val aliasAnalysisTimeLimit: Duration = 10.seconds, ) - private val aliasInfo by lazy { compute() } + private val mayAliasInfo by lazy { compute(MergeType.May) } + private val mustAliasInfo by lazy { compute(MergeType.Must) } class MethodAliasInfo( val aliasBeforeStatement: Array>?>?, @@ -38,26 +40,31 @@ class JIRLocalAliasAnalysis( it !is AliasApInfo || it.accessors.isNotEmpty() || it.base != base }?.map { it.wrapAliasInfo() } + fun findMustAlias(base: AccessPathBase.LocalVar, statement: CommonInst): List? { + val idx = languageManager.getInstIndex(statement) + return getLocalVarAliases(mustAliasInfo.aliasBeforeStatement, idx, base) + } + fun findAlias(base: AccessPathBase.LocalVar, statement: CommonInst): List? { - val aliasBefore = aliasInfo.aliasBeforeStatement ?: return null + val aliasBefore = mayAliasInfo.aliasBeforeStatement ?: return null val idx = languageManager.getInstIndex(statement) return getLocalVarAliases(aliasBefore, idx, base) } fun getAllAliasAtStatement(statement: CommonInst): Int2ObjectOpenHashMap> { - val aliasBefore = aliasInfo.aliasBeforeStatement ?: return Int2ObjectOpenHashMap() + val aliasBefore = mayAliasInfo.aliasBeforeStatement ?: return Int2ObjectOpenHashMap() val idx = languageManager.getInstIndex(statement) return aliasBefore[idx]?.let { wrapAllInfo(it) } ?: Int2ObjectOpenHashMap() } fun findAliasAfterStatement(base: AccessPathBase.LocalVar, statement: CommonInst): List? { - val aliasAfter = aliasInfo.aliasAfterStatement ?: return null + val aliasAfter = mayAliasInfo.aliasAfterStatement ?: return null val idx = languageManager.getInstIndex(statement) return getLocalVarAliases(aliasAfter, idx, base) } - private fun compute(): MethodAliasInfo { - return JIRIntraProcAliasAnalysis(entryPoint, graph, callResolver, languageManager, params).compute(localVariableReachability) + private fun compute(mergeType: MergeType): MethodAliasInfo { + return JIRIntraProcAliasAnalysis(entryPoint, graph, callResolver, languageManager, params, mergeType).compute(localVariableReachability) } sealed interface AliasAccessor { diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt index dadffd372..aea507aed 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt @@ -4,8 +4,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.ints.IntCollection -import it.unimi.dsi.fastutil.ints.IntIntImmutablePair -import it.unimi.dsi.fastutil.ints.IntIntMutablePair import it.unimi.dsi.fastutil.ints.IntOpenHashSet import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAccessor import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalVariableReachability @@ -13,7 +11,6 @@ import org.opentaint.dataflow.jvm.ap.ifds.alias.JIRIntraProcAliasAnalysis.JIRIns import org.opentaint.dataflow.jvm.ap.ifds.alias.RefValue.Local import org.opentaint.dataflow.util.forEachInt import org.opentaint.dataflow.util.forEachIntEntry -import org.opentaint.dataflow.util.mapIntTo import org.opentaint.ir.api.jvm.JIRField import org.opentaint.ir.api.jvm.JIRMethod import org.opentaint.ir.api.jvm.cfg.JIRInst @@ -23,7 +20,8 @@ import java.util.BitSet class DSUAliasAnalysis( val methodCallResolver: CallResolver, val rootMethodReachabilityInfo: JIRLocalVariableReachability, - val cancellation: AnalysisCancellation + val mergeType: MergeType, + val cancellation: AnalysisCancellation, ) { private val aliasManager = AAInfoManager() private val dsuMergeStrategy = DsuMergeStrategy(aliasManager) @@ -74,194 +72,6 @@ class DSUAliasAnalysis( override fun createLocal(idx: Int): Local = Local(idx, ContextInfo.rootContext) } - interface ImmutableState { - fun mutableCopy(): State - } - - class State private constructor( - val manager: AAInfoManager, - val aliasGroups: IntDisjointSets, - ) : ImmutableState { - - override fun hashCode(): Int = error("Unsupported operation") - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is State) return false - - /** - * We don't need to align heap instances here. - * Since set repr selection is deterministic due to strategy, - * if sets are equal their repr are also equal. - * So, heap instances must be equal. - * */ - return aliasGroups == other.aliasGroups - } - - fun asImmutable(): ImmutableState = this - - override fun mutableCopy(): State = State(manager, aliasGroups.mutableCopy()) - - fun removeUnsafe(infos: IntOpenHashSet): State { - if (infos.isEmpty()) return this - - val normalizedInfos = fixHeapElementInstance(infos) - val result = aliasGroups.mutableCopy() - - val removedInstances = IntOpenHashSet() - result.prepareRemoveAll(normalizedInfos, removedInstances) - - val removeAfterHeapFix = IntOpenHashSet() - restoreHeapInvariant(manager, result, removeAfterHeapFix) - - // since we use prepare-remove, old replaced roots are still in the DSU - removeAfterHeapFix.addAll(normalizedInfos) - result.removeAll(removeAfterHeapFix) - - if (removedInstances.isEmpty()) { - return State(manager, result) - } - - val removedHeap = IntOpenHashSet() - result.allElements().forEachInt { - if (!manager.isHeapAlias(it)) return@forEachInt - - val heapElement = manager.getHeapRefUnchecked(it) - if (removedInstances.contains(heapElement.instance)) { - removedHeap.add(it) - } - } - - return State(manager, result).removeUnsafe(removedHeap) - } - - fun aliasGroupId(info: Int): Int = aliasGroups.find(info) - fun aliasGroupRepr(groupId: Int): Int = aliasGroups.find(groupId) - - fun mergeAliasSets(aliasSets: IntOpenHashSet): State { - if (aliasSets.size < 2) return this - - val firstRepr = aliasSets.intIterator().nextInt() - val relations = mutableListOf() - aliasSets.forEachInt { - if (it == firstRepr) return@forEachInt - relations += IntIntMutablePair(firstRepr, it) - } - - val result = aliasGroups.mutableCopy() - mergeUnionRelations(relations, result, manager) - - return State(manager, result) - } - - fun forEachAliasInSet(info: Int, body: (Int) -> Unit) = forEachAliasInSetWithBreak(info, body) - - fun forEachAliasInSetWithBreak(info: Int, body: (Int) -> Unit?) { - aliasGroups.forEachElementInSet(info, body) - } - - fun allAliasSets(): Collection = aliasGroups.allSets() - - fun allSetElements(): IntOpenHashSet = aliasGroups.allElements() - - override fun toString(): String = buildString { - for (aliasSet in allAliasSets()) { - appendLine("{") - aliasSet.forEachInt { - appendLine("\t($it) -> ${manager.getElementUncheck(it)}") - } - appendLine("}") - } - } - - private fun fixHeapElementInstance(elements: IntOpenHashSet) = - elements.mapIntTo(IntOpenHashSet(elements.size)) { - ensureHeapElementCorrect(it, aliasGroups, manager) - } - - companion object { - fun empty(manager: AAInfoManager, strategy: DsuMergeStrategy): State = - State(manager, IntDisjointSets(strategy)) - - private fun restoreHeapInvariant( - manager: AAInfoManager, - state: IntDisjointSets, - elementsToRemove: IntOpenHashSet, - ) { - while (true) { - val replacements = mutableListOf() - - state.allElements().forEachInt { elementIdx -> - if (elementsToRemove.contains(elementIdx)) return@forEachInt - - val fixedHeap = ensureHeapElementCorrect(elementIdx, state, manager) - if (fixedHeap == elementIdx) return@forEachInt - - replacements += IntIntImmutablePair(elementIdx, fixedHeap) - } - - if (replacements.isEmpty()) return - - for (replacement in replacements) { - elementsToRemove.add(replacement.leftInt()) - state.union(replacement.leftInt(), replacement.rightInt()) - } - } - } - - private fun ensureHeapElementCorrect(element: Int, state: IntDisjointSets, manager: AAInfoManager): Int { - if (!manager.isHeapAlias(element)) return element - - val heapElement = manager.getHeapRefUnchecked(element) - val heapInstanceRepr = state.find(heapElement.instance) - if (heapInstanceRepr == heapElement.instance) return element - - return manager.replaceHeapInstance(element, heapInstanceRepr) - } - - fun merge(manager: AAInfoManager, strategy: DsuMergeStrategy, states: List): State { - val allElementParentRelations = mutableListOf() - states.forEach { s -> - val stateDsu = (s as State).aliasGroups - stateDsu.collectElementParentPairs(allElementParentRelations) - } - - val result = IntDisjointSets(strategy) - mergeUnionRelations(allElementParentRelations, result, manager) - - return State(manager, result) - } - - private fun mergeUnionRelations( - relations: List, - result: IntDisjointSets, - manager: AAInfoManager - ) { - val removedElements = IntOpenHashSet() - while (true) { - var modified = false - relations.forEach { - val status = result.union(it.leftInt(), it.rightInt()) - modified = modified or status - } - - if (!modified) break - - restoreHeapInvariant(manager, result, removedElements) - - relations.forEach { relation -> - val fixedLeft = ensureHeapElementCorrect(relation.leftInt(), result, manager) - val fixedRight = ensureHeapElementCorrect(relation.rightInt(), result, manager) - - relation.left(fixedLeft) - relation.right(fixedRight) - } - } - result.removeAll(removedElements) - } - } - } - private fun AAInfo.index(): Int { return aliasManager.getOrAdd(this) } @@ -342,7 +152,7 @@ class DSUAliasAnalysis( private fun merge(inst: JIRInst, states: Int2ObjectMap, call: CallTreeNode): ImmutableState { val statesToMerge = states.values.filterNotNull() - val merged = State.merge(aliasManager, dsuMergeStrategy, statesToMerge) + val merged = State.merge(aliasManager, dsuMergeStrategy, statesToMerge, mergeType) val reachabilityInfo = methodReachabilityInfo(inst.location.method) val instIdx = inst.location.index @@ -440,7 +250,7 @@ class DSUAliasAnalysis( return statesAfterCall.first().mutableCopy() } - return State.merge(aliasManager, dsuMergeStrategy, statesAfterCall) + return State.merge(aliasManager, dsuMergeStrategy, statesAfterCall, mergeType) } private fun State.invalidateOuterHeapAliases(startInvalidAliases: IntOpenHashSet): State { diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUState.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUState.kt new file mode 100644 index 000000000..6b607753c --- /dev/null +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUState.kt @@ -0,0 +1,247 @@ +package org.opentaint.dataflow.jvm.ap.ifds.alias + +import it.unimi.dsi.fastutil.ints.IntArrayList +import it.unimi.dsi.fastutil.ints.IntIntImmutablePair +import it.unimi.dsi.fastutil.ints.IntIntMutablePair +import it.unimi.dsi.fastutil.ints.IntOpenHashSet +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap +import org.opentaint.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.DsuMergeStrategy +import org.opentaint.dataflow.util.forEachInt +import org.opentaint.dataflow.util.mapIntTo +import kotlin.collections.forEach + +interface ImmutableState { + fun mutableCopy(): State +} + +enum class MergeType{ + May, Must +} + +class State private constructor( + val manager: AAInfoManager, + val aliasGroups: IntDisjointSets, +) : ImmutableState { + + override fun hashCode(): Int = error("Unsupported operation") + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is State) return false + + /** + * We don't need to align heap instances here. + * Since set repr selection is deterministic due to strategy, + * if sets are equal their repr are also equal. + * So, heap instances must be equal. + * */ + return aliasGroups == other.aliasGroups + } + + fun asImmutable(): ImmutableState = this + + override fun mutableCopy(): State = State(manager, aliasGroups.mutableCopy()) + + fun removeUnsafe(infos: IntOpenHashSet): State { + if (infos.isEmpty()) return this + + val normalizedInfos = fixHeapElementInstance(infos) + val result = aliasGroups.mutableCopy() + + val removedInstances = IntOpenHashSet() + result.prepareRemoveAll(normalizedInfos, removedInstances) + + val removeAfterHeapFix = IntOpenHashSet() + restoreHeapInvariant(manager, result, removeAfterHeapFix) + + // since we use prepare-remove, old replaced roots are still in the DSU + removeAfterHeapFix.addAll(normalizedInfos) + result.removeAll(removeAfterHeapFix) + + if (removedInstances.isEmpty()) { + return State(manager, result) + } + + val removedHeap = IntOpenHashSet() + result.allElements().forEachInt { + if (!manager.isHeapAlias(it)) return@forEachInt + + val heapElement = manager.getHeapRefUnchecked(it) + if (removedInstances.contains(heapElement.instance)) { + removedHeap.add(it) + } + } + + return State(manager, result).removeUnsafe(removedHeap) + } + + fun aliasGroupId(info: Int): Int = aliasGroups.find(info) + fun aliasGroupRepr(groupId: Int): Int = aliasGroups.find(groupId) + + fun mergeAliasSets(aliasSets: IntOpenHashSet): State { + if (aliasSets.size < 2) return this + + val firstRepr = aliasSets.intIterator().nextInt() + val relations = mutableListOf() + aliasSets.forEachInt { + if (it == firstRepr) return@forEachInt + relations += IntIntMutablePair(firstRepr, it) + } + + val result = aliasGroups.mutableCopy() + mergeUnionRelations(relations, result, manager) + + return State(manager, result) + } + + fun forEachAliasInSet(info: Int, body: (Int) -> Unit) = forEachAliasInSetWithBreak(info, body) + + fun forEachAliasInSetWithBreak(info: Int, body: (Int) -> Unit?) { + aliasGroups.forEachElementInSet(info, body) + } + + fun allAliasSets(): Collection = aliasGroups.allSets() + + fun allSetElements(): IntOpenHashSet = aliasGroups.allElements() + + override fun toString(): String = buildString { + for (aliasSet in allAliasSets()) { + appendLine("{") + aliasSet.forEachInt { + appendLine("\t($it) -> ${manager.getElementUncheck(it)}") + } + appendLine("}") + } + } + + private fun fixHeapElementInstance(elements: IntOpenHashSet) = + elements.mapIntTo(IntOpenHashSet(elements.size)) { + ensureHeapElementCorrect(it, aliasGroups, manager) + } + + companion object { + fun empty(manager: AAInfoManager, strategy: DsuMergeStrategy): State = + State(manager, IntDisjointSets(strategy)) + + private fun restoreHeapInvariant( + manager: AAInfoManager, + state: IntDisjointSets, + elementsToRemove: IntOpenHashSet, + ) { + while (true) { + val replacements = mutableListOf() + + state.allElements().forEachInt { elementIdx -> + if (elementsToRemove.contains(elementIdx)) return@forEachInt + + val fixedHeap = ensureHeapElementCorrect(elementIdx, state, manager) + if (fixedHeap == elementIdx) return@forEachInt + + replacements += IntIntImmutablePair(elementIdx, fixedHeap) + } + + if (replacements.isEmpty()) return + + for (replacement in replacements) { + elementsToRemove.add(replacement.leftInt()) + state.union(replacement.leftInt(), replacement.rightInt()) + } + } + } + + private fun ensureHeapElementCorrect(element: Int, state: IntDisjointSets, manager: AAInfoManager): Int { + if (!manager.isHeapAlias(element)) return element + + val heapElement = manager.getHeapRefUnchecked(element) + val heapInstanceRepr = state.find(heapElement.instance) + if (heapInstanceRepr == heapElement.instance) return element + + return manager.replaceHeapInstance(element, heapInstanceRepr) + } + + fun merge(manager: AAInfoManager, strategy: DsuMergeStrategy, states: List, mergeType: MergeType): State { + val allAliasGroups = states.map { (it as State).aliasGroups } + + val relations = when (mergeType) { + MergeType.May -> mergeMay(allAliasGroups) + MergeType.Must -> mergeMust(allAliasGroups) + } + + val result = IntDisjointSets(strategy) + mergeUnionRelations(relations, result, manager) + + return State(manager, result) + } + + fun mergeMay(allAliasGroups: List): List { + val allElementParentRelations = mutableListOf() + allAliasGroups.forEach { a -> + a.collectElementParentPairs(allElementParentRelations) + } + return allElementParentRelations + } + + fun mergeMust(allAliasGroups: List): List { + val elementsInAll = IntOpenHashSet() + val allSetElements = allAliasGroups.map { it.allElements() } + var first = true + allSetElements.forEach { set -> + if (first) { + elementsInAll.addAll(set) + first = false + return@forEach + } + elementsInAll.removeAll { element -> !set.contains(element) } + } + + if (elementsInAll.isEmpty()) return emptyList() + + val resultRelations = mutableListOf() + val map = Object2IntOpenHashMap() + val totalStates = allAliasGroups.size + elementsInAll.forEachInt { element -> + val elementSignature = IntArrayList(totalStates) + allAliasGroups.forEach { aliasGroup -> + elementSignature.add(aliasGroup.find(element)) + } + if (map.contains(elementSignature)) { + val parent = map.getInt(elementSignature) + resultRelations.add(IntIntMutablePair(parent, element)) + } + else { + map.put(elementSignature, element) + } + } + + return resultRelations + } + + private fun mergeUnionRelations( + relations: List, + result: IntDisjointSets, + manager: AAInfoManager + ) { + val removedElements = IntOpenHashSet() + while (true) { + var modified = false + relations.forEach { + val status = result.union(it.leftInt(), it.rightInt()) + modified = modified or status + } + + if (!modified) break + + restoreHeapInvariant(manager, result, removedElements) + + relations.forEach { relation -> + val fixedLeft = ensureHeapElementCorrect(relation.leftInt(), result, manager) + val fixedRight = ensureHeapElementCorrect(relation.rightInt(), result, manager) + + relation.left(fixedLeft) + relation.right(fixedRight) + } + } + result.removeAll(removedElements) + } + } +} diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt index 141ae187b..afe90328f 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt @@ -28,6 +28,7 @@ class JIRIntraProcAliasAnalysis( private val callResolver: JIRCallResolver, private val languageManager: JIRLanguageManager, private val params: JIRLocalAliasAnalysis.Params, + private val mergeType: MergeType, ) { companion object { private val logger = object : KLogging() {}.logger @@ -82,7 +83,7 @@ class JIRIntraProcAliasAnalysis( localVariableReachability: JIRLocalVariableReachability ): JIRLocalAliasAnalysis.MethodAliasInfo { val jig = getJIG(entryPoint) - val daa = DSUAliasAnalysis(CallResolver(), localVariableReachability, cancellation).analyze(jig) + val daa = DSUAliasAnalysis(CallResolver(), localVariableReachability, mergeType, cancellation).analyze(jig) val aliasBeforeStatement = Array(jig.statements.size) { i -> resolveLocalVar(daa.statesBeforeStmt[i], localVariableReachability, i) diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/taint/JIRBasicAtomEvaluator.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/taint/JIRBasicAtomEvaluator.kt index 1eb0bc8c5..7fe4b297e 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/taint/JIRBasicAtomEvaluator.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/taint/JIRBasicAtomEvaluator.kt @@ -294,12 +294,16 @@ class JIRBasicAtomEvaluator( val base = AccessPathBase.LocalVar(lv.index) if (!matchArrayValue) { - // todo: use must alias if negated - val aliasInfo = aa.findAlias(base, statement) + val aliasInfo = + if (negated) + aa.findMustAlias(base, statement) + else + aa.findAlias(base, statement) if (aliasInfo != null) { body(aliasInfo) } } else { + // should `negated` change behaviour here like in the if-case? val allAliases = aa.getAllAliasAtStatement(statement) for ((_, aliasSet) in allAliases) { for (info in aliasSet) { diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/BasicTestUtils.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/BasicTestUtils.kt index 5049e6d7f..961cf3bc6 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/BasicTestUtils.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/BasicTestUtils.kt @@ -4,14 +4,34 @@ import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.TestInstance +import org.opentaint.dataflow.ap.ifds.AccessPathBase +import org.opentaint.dataflow.ifds.SingletonUnit +import org.opentaint.dataflow.ifds.UnitType +import org.opentaint.dataflow.ifds.UnknownUnit +import org.opentaint.dataflow.jvm.ap.ifds.JIRCallResolver +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.JIRLocalVariableReachability +import org.opentaint.dataflow.jvm.ap.ifds.analysis.JIRAnalysisManager +import org.opentaint.dataflow.jvm.ifds.JIRUnitResolver import org.opentaint.ir.api.jvm.JIRClasspath import org.opentaint.ir.api.jvm.JIRDatabase +import org.opentaint.ir.api.jvm.JIRMethod +import org.opentaint.ir.api.jvm.RegisteredLocation +import org.opentaint.ir.api.jvm.cfg.JIRCallInst +import org.opentaint.ir.api.jvm.cfg.JIRInst +import org.opentaint.ir.api.jvm.cfg.JIRLocalVar +import org.opentaint.ir.api.jvm.cfg.JIRValue import org.opentaint.ir.impl.JIRRamErsSettings import org.opentaint.ir.impl.features.InMemoryHierarchy import org.opentaint.ir.impl.features.Usages import org.opentaint.ir.impl.features.classpaths.UnknownClasses +import org.opentaint.ir.impl.features.usagesExt import org.opentaint.ir.impl.opentaintIrDb +import org.opentaint.jvm.graph.JApplicationGraphImpl import java.nio.file.Path +import kotlin.collections.orEmpty import kotlin.io.path.Path @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -19,6 +39,8 @@ abstract class BasicTestUtils { protected lateinit var samplesJar: Path protected lateinit var db: JIRDatabase protected lateinit var cp: JIRClasspath + + protected val manager by lazy { JIRAnalysisManager(cp) } @BeforeAll fun setup() { @@ -59,4 +81,73 @@ abstract class BasicTestUtils { protected fun findMethod(className: String, methodName: String) = findClass(className).declaredMethods.find { it.name == methodName } ?: error("Method $methodName not found in $className") + + protected fun aaForMethod( + method: JIRMethod, + params: JIRLocalAliasAnalysis.Params = JIRLocalAliasAnalysis.Params() + ): JIRLocalAliasAnalysis { + val ep = method.instList.first() + val usages = runBlocking { cp.usagesExt() } + val graph = JApplicationGraphImpl(cp, usages) + + val callResolver = JIRCallResolver(cp, SingleLocationUnit(method.enclosingClass.declaration.location)) + val localReachability = JIRLocalVariableReachability(method, graph, manager) + + return JIRLocalAliasAnalysis(ep, graph, callResolver, localReachability, manager, params) + } + + protected fun interProcParams(depth: Int) = + JIRLocalAliasAnalysis.Params(useAliasAnalysis = true, aliasAnalysisInterProcCallDepth = depth) + + protected fun JIRMethod.findSinkCall(sinkName: String): JIRCallInst = + instList.filterIsInstance().first { it.callExpr.method.name == sinkName } + + protected fun JIRLocalAliasAnalysis.valueApAliases(value: JIRValue, stmt: JIRInst): List = + valueAliases(value, stmt).filterIsInstance() + + protected fun JIRLocalAliasAnalysis.sinkArgApAliases(sink: JIRCallInst): List = + valueApAliases(sink.callExpr.args[0], sink) + + abstract fun JIRLocalAliasAnalysis.getAliases( + base: AccessPathBase.LocalVar, + statement: JIRInst + ): List? + + protected fun JIRLocalAliasAnalysis.valueAliases( + value: JIRValue, + stmt: JIRInst + ): List { + check(value is JIRLocalVar) { "Only local var aliases supported" } + return getAliases(AccessPathBase.LocalVar(value.index), stmt).orEmpty() + } + + protected fun AliasApInfo.isPlainBase(expected: AccessPathBase): Boolean = + accessors.isEmpty() && base == expected + + protected fun AliasAccessor.isField(name: String): Boolean = + this is AliasAccessor.Field && this.fieldName == name + + protected fun List.singleFieldNamed(name: String): Boolean = + size == 1 && single().isField(name) + + protected class SingleLocationUnit(val loc: RegisteredLocation) : JIRUnitResolver { + override fun resolve(method: JIRMethod): UnitType = + if (method.enclosingClass.declaration.location == loc) SingletonUnit else UnknownUnit + + override fun locationIsUnknown(loc: RegisteredLocation): Boolean = loc != this.loc + } + + companion object { + const val ALIAS_SAMPLE_PKG = "sample.alias" + const val SIMPLE_SAMPLE = "$ALIAS_SAMPLE_PKG.SimpleAliasSample" + const val LOOP_SAMPLE = "$ALIAS_SAMPLE_PKG.LoopAliasSample" + const val HEAP_SAMPLE = "$ALIAS_SAMPLE_PKG.HeapAliasSample" + const val INTERPROC_SAMPLE = "$ALIAS_SAMPLE_PKG.InterProcAliasSample" + + protected const val FIELD_VALUE = "value" + protected const val FIELD_BOX = "box" + protected const val FIELD_NEXT = "next" + protected const val FIELD_DATA = "data" + protected const val FIELD_INTERPROC = "field" + } } diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysisStateTest.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysisStateTest.kt index a42ea18ec..abb305b63 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysisStateTest.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysisStateTest.kt @@ -2,7 +2,6 @@ package org.opentaint.dataflow.jvm.ap.ifds.alias import it.unimi.dsi.fastutil.ints.IntOpenHashSet import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAccessor.Field -import org.opentaint.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.State import org.opentaint.dataflow.jvm.ap.ifds.alias.LocalAlias.SimpleLoc import java.util.IdentityHashMap import kotlin.test.Test @@ -72,7 +71,7 @@ class DSUAliasAnalysisStateTest { fun mergeStates(vararg builders: StateBuilder) { val states = builders.map { it.state } - this.state = State.merge(manager, strategy, states) + this.state = State.merge(manager, strategy, states, MergeType.May) builders.forEach { created.putAll(it.created) diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/AliasSampleTest.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAliasSampleTest.kt similarity index 67% rename from core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/AliasSampleTest.kt rename to core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAliasSampleTest.kt index 8f069b07d..1935426b0 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/AliasSampleTest.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAliasSampleTest.kt @@ -1,114 +1,22 @@ package org.opentaint.dataflow.jvm.ap.ifds.alias -import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.TestInstance import org.opentaint.dataflow.ap.ifds.AccessPathBase import org.opentaint.dataflow.ap.ifds.AccessPathBase.Companion.Argument -import org.opentaint.dataflow.ap.ifds.access.FactAp -import org.opentaint.dataflow.ap.ifds.access.InitialFactAp -import org.opentaint.dataflow.configuration.jvm.TaintCleaner -import org.opentaint.dataflow.configuration.jvm.TaintEntryPointSource -import org.opentaint.dataflow.configuration.jvm.TaintMethodEntrySink -import org.opentaint.dataflow.configuration.jvm.TaintMethodExitSink -import org.opentaint.dataflow.configuration.jvm.TaintMethodExitSource -import org.opentaint.dataflow.configuration.jvm.TaintMethodSink -import org.opentaint.dataflow.configuration.jvm.TaintMethodSource -import org.opentaint.dataflow.configuration.jvm.TaintPassThrough -import org.opentaint.dataflow.configuration.jvm.TaintStaticFieldSource -import org.opentaint.dataflow.ifds.SingletonUnit -import org.opentaint.dataflow.ifds.UnitType -import org.opentaint.dataflow.ifds.UnknownUnit import org.opentaint.dataflow.jvm.BasicTestUtils -import org.opentaint.dataflow.jvm.ap.ifds.JIRCallResolver 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.JIRLocalVariableReachability -import org.opentaint.dataflow.jvm.ap.ifds.analysis.JIRAnalysisManager -import org.opentaint.dataflow.jvm.ap.ifds.taint.TaintRulesProvider -import org.opentaint.dataflow.jvm.ifds.JIRUnitResolver -import org.opentaint.ir.api.common.CommonMethod -import org.opentaint.ir.api.common.cfg.CommonInst -import org.opentaint.ir.api.jvm.JIRField -import org.opentaint.ir.api.jvm.JIRMethod -import org.opentaint.ir.api.jvm.RegisteredLocation -import org.opentaint.ir.api.jvm.cfg.JIRCallInst import org.opentaint.ir.api.jvm.cfg.JIRInst -import org.opentaint.ir.api.jvm.cfg.JIRLocalVar -import org.opentaint.ir.api.jvm.cfg.JIRValue -import org.opentaint.ir.impl.features.usagesExt -import org.opentaint.jvm.graph.JApplicationGraphImpl import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertTrue @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class AliasSampleTest : BasicTestUtils() { - private val noRules = object : TaintRulesProvider { - override fun entryPointRulesForMethod( - method: CommonMethod, - fact: FactAp?, - allRelevant: Boolean - ): Iterable = emptyList() - - override fun sourceRulesForMethod( - method: CommonMethod, - statement: CommonInst, - fact: FactAp?, - allRelevant: Boolean - ): Iterable = emptyList() - - override fun exitSourceRulesForMethod( - method: CommonMethod, - statement: CommonInst, - fact: FactAp?, - allRelevant: Boolean - ): Iterable = emptyList() - - override fun sinkRulesForMethod( - method: CommonMethod, - statement: CommonInst, - fact: FactAp?, - allRelevant: Boolean - ): Iterable = emptyList() - - override fun sinkRulesForMethodEntry( - method: CommonMethod, - fact: FactAp?, - allRelevant: Boolean - ): Iterable = emptyList() - - override fun sinkRulesForMethodExit( - method: CommonMethod, - statement: CommonInst, - fact: FactAp?, - initialFacts: Set?, - allRelevant: Boolean - ): Iterable = emptyList() - - override fun passTroughRulesForMethod( - method: CommonMethod, - statement: CommonInst, - fact: FactAp?, - allRelevant: Boolean - ): Iterable = emptyList() - - override fun cleanerRulesForMethod( - method: CommonMethod, - statement: CommonInst, - fact: FactAp?, - allRelevant: Boolean - ): Iterable = emptyList() - - override fun sourceRulesForStaticField( - field: JIRField, - statement: CommonInst, - fact: FactAp?, - allRelevant: Boolean - ): Iterable = emptyList() - } - - private val manager by lazy { JIRAnalysisManager(cp, noRules) } +class MayAliasSampleTest : BasicTestUtils() { + override fun JIRLocalAliasAnalysis.getAliases( + base: AccessPathBase.LocalVar, + statement: JIRInst + ): List? = findAlias(base, statement) @Test fun `test simple aliasing`() { @@ -549,68 +457,4 @@ class AliasSampleTest : BasicTestUtils() { assertFalse { apAliases.any { it.isPlainBase(Argument(0)) } } } - - private fun aaForMethod( - method: JIRMethod, - params: JIRLocalAliasAnalysis.Params = JIRLocalAliasAnalysis.Params() - ): JIRLocalAliasAnalysis { - val ep = method.instList.first() - val usages = runBlocking { cp.usagesExt() } - val graph = JApplicationGraphImpl(cp, usages) - - val callResolver = JIRCallResolver(cp, SingleLocationUnit(method.enclosingClass.declaration.location)) - val localReachability = JIRLocalVariableReachability(method, graph, manager) - - return JIRLocalAliasAnalysis(ep, graph, callResolver, localReachability, manager, params) - } - - private fun interProcParams(depth: Int) = - JIRLocalAliasAnalysis.Params(useAliasAnalysis = true, aliasAnalysisInterProcCallDepth = depth) - - private fun JIRMethod.findSinkCall(sinkName: String): JIRCallInst = - instList.filterIsInstance().first { it.callExpr.method.name == sinkName } - - private fun JIRLocalAliasAnalysis.valueApAliases(value: JIRValue, stmt: JIRInst): List = - valueAliases(value, stmt).filterIsInstance() - - private fun JIRLocalAliasAnalysis.sinkArgApAliases(sink: JIRCallInst): List = - valueApAliases(sink.callExpr.args[0], sink) - - private fun JIRLocalAliasAnalysis.valueAliases( - value: JIRValue, - stmt: JIRInst - ): List { - check(value is JIRLocalVar) { "Only local var aliases supported" } - return findAlias(AccessPathBase.LocalVar(value.index), stmt).orEmpty() - } - - private fun AliasApInfo.isPlainBase(expected: AccessPathBase): Boolean = - accessors.isEmpty() && base == expected - - private fun AliasAccessor.isField(name: String): Boolean = - this is AliasAccessor.Field && this.fieldName == name - - private fun List.singleFieldNamed(name: String): Boolean = - size == 1 && single().isField(name) - - private class SingleLocationUnit(val loc: RegisteredLocation) : JIRUnitResolver { - override fun resolve(method: JIRMethod): UnitType = - if (method.enclosingClass.declaration.location == loc) SingletonUnit else UnknownUnit - - override fun locationIsUnknown(loc: RegisteredLocation): Boolean = loc != this.loc - } - - companion object { - const val ALIAS_SAMPLE_PKG = "sample.alias" - const val SIMPLE_SAMPLE = "$ALIAS_SAMPLE_PKG.SimpleAliasSample" - const val LOOP_SAMPLE = "$ALIAS_SAMPLE_PKG.LoopAliasSample" - const val HEAP_SAMPLE = "$ALIAS_SAMPLE_PKG.HeapAliasSample" - const val INTERPROC_SAMPLE = "$ALIAS_SAMPLE_PKG.InterProcAliasSample" - - private const val FIELD_VALUE = "value" - private const val FIELD_BOX = "box" - private const val FIELD_NEXT = "next" - private const val FIELD_DATA = "data" - private const val FIELD_INTERPROC = "field" - } } diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MustAliasSampleTest.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MustAliasSampleTest.kt new file mode 100644 index 000000000..06b8050dd --- /dev/null +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MustAliasSampleTest.kt @@ -0,0 +1,457 @@ +package org.opentaint.dataflow.jvm.ap.ifds.alias + +import org.junit.jupiter.api.TestInstance +import org.opentaint.dataflow.ap.ifds.AccessPathBase +import org.opentaint.dataflow.ap.ifds.AccessPathBase.Companion.Argument +import org.opentaint.dataflow.jvm.BasicTestUtils +import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis +import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAccessor +import org.opentaint.ir.api.jvm.cfg.JIRInst +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class MustAliasSampleTest : BasicTestUtils() { + override fun JIRLocalAliasAnalysis.getAliases( + base: AccessPathBase.LocalVar, + statement: JIRInst + ): List? = findMustAlias(base, statement) + + @Test + fun `test simple aliasing`() { + val method = findMethod(SIMPLE_SAMPLE, "simpleArgAlias") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("testSimpleArgAlias") + val apAliases = aa.sinkArgApAliases(sink) + + assertFalse { apAliases.any { it.isPlainBase(Argument(0)) } } + assertFalse { apAliases.any { it.isPlainBase(Argument(1)) } } + } + + @Test + fun `test alias in while loop`() { + val method = findMethod(LOOP_SAMPLE, "aliasInLoop") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertFalse { apAliases.any { it.isPlainBase(Argument(0)) } } + assertFalse { apAliases.any { it.isPlainBase(Argument(1)) } } + } + + @Test + fun `test alias in for-each loop`() { + val method = findMethod(LOOP_SAMPLE, "aliasInForEachLoop") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertFalse { apAliases.any { it.isPlainBase(Argument(0)) } } + } + + @Test + fun `test alias in try-catch both branches`() { + val method = findMethod(LOOP_SAMPLE, "aliasInTryCatch") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertFalse { apAliases.any { it.isPlainBase(Argument(0)) } } + assertFalse { apAliases.any { it.isPlainBase(Argument(1)) } } + } + + @Test + fun `test alias in try only`() { + val method = findMethod(LOOP_SAMPLE, "aliasInTryOnly") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(0)) } } + } + + @Test + fun `test node next loop - loop diverges field chain`() { + val method = findMethod(LOOP_SAMPLE, "nodeNextLoop") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertFalse { apAliases.any { it.isPlainBase(Argument(0)) } } + assertFalse { apAliases.any { it.base == Argument(0) && it.accessors.isNotEmpty() } } + } + + @Test + fun `test node next loop data - loop diverges field chain`() { + val method = findMethod(LOOP_SAMPLE, "nodeNextLoopData") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertFalse { + apAliases.any { + it.base == Argument(0) + && it.accessors.size == 1 + && it.accessors.single().isField(FIELD_DATA) + } + } + assertFalse { + apAliases.any { + it.base == Argument(0) + && it.accessors.size >= 2 + && it.accessors.last().isField(FIELD_DATA) + && it.accessors.dropLast(1).all { a -> a.isField(FIELD_NEXT) } + } + } + } + + @Test + fun `test read argument field`() { + val method = findMethod(HEAP_SAMPLE, "readArgField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test write then read argument field`() { + val method = findMethod(HEAP_SAMPLE, "writeArgField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(1)) } } + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test read argument deep field`() { + val method = findMethod(HEAP_SAMPLE, "readArgDeepField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == Argument(0) + && it.accessors.size == 2 + && it.accessors[0].isField(FIELD_BOX) + && it.accessors[1].isField(FIELD_VALUE) + } + } + } + + @Test + fun `test write then read argument deep field`() { + val method = findMethod(HEAP_SAMPLE, "writeArgDeepField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(1)) } } + assertTrue { + apAliases.any { + it.base == Argument(0) + && it.accessors.size == 2 + && it.accessors[0].isField(FIELD_BOX) + && it.accessors[1].isField(FIELD_VALUE) + } + } + } + + @Test + fun `test read argument array element`() { + val method = findMethod(HEAP_SAMPLE, "readArgArrayElement") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleOrNull() == AliasAccessor.Array + } + } + } + + @Test + fun `test write then read argument array element`() { + val method = findMethod(HEAP_SAMPLE, "writeArgArrayElement") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(1)) } } + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleOrNull() == AliasAccessor.Array + } + } + } + + @Test + fun `test field to field copy`() { + val method = findMethod(HEAP_SAMPLE, "fieldToField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + assertTrue { + apAliases.any { + it.base == Argument(1) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test swap fields`() { + val method = findMethod(HEAP_SAMPLE, "swapFields") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkTwoValues") + + val aValueAliases = aa.valueApAliases(sink.callExpr.args[0], sink) + assertTrue { + aValueAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + assertTrue { + aValueAliases.any { + it.base == Argument(1) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + + val bValueAliases = aa.valueApAliases(sink.callExpr.args[1], sink) + assertTrue { + bValueAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + assertTrue { + bValueAliases.any { + it.base == Argument(1) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test array element to field`() { + val method = findMethod(HEAP_SAMPLE, "arrayToField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleOrNull() == AliasAccessor.Array + } + } + assertTrue { + apAliases.any { + it.base == Argument(1) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test field to array element`() { + val method = findMethod(HEAP_SAMPLE, "fieldToArray") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + assertTrue { + apAliases.any { + it.base == Argument(1) && it.accessors.singleOrNull() == AliasAccessor.Array + } + } + } + + @Test + fun `test node traversal - loop diverges field chain`() { + val method = findMethod(HEAP_SAMPLE, "nodeTraversal") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertFalse { apAliases.any { it.isPlainBase(Argument(0)) } } + assertFalse { + apAliases.any { + it.base == Argument(0) + && it.accessors.isNotEmpty() + && it.accessors.all { a -> a.isField(FIELD_NEXT) } + } + } + } + + @Test + fun `test node traversal data - loop diverges field chain`() { + val method = findMethod(HEAP_SAMPLE, "nodeTraversalData") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertFalse { + apAliases.any { + it.base == Argument(0) + && it.accessors.size == 1 + && it.accessors.single().isField(FIELD_DATA) + } + } + assertFalse { + apAliases.any { + it.base == Argument(0) + && it.accessors.size >= 2 + && it.accessors.last().isField(FIELD_DATA) + && it.accessors.dropLast(1).all { a -> a.isField(FIELD_NEXT) } + } + } + } + + @Test + fun `test field overwrite on argument receiver`() { + val method = findMethod(HEAP_SAMPLE, "fieldOverwrite") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertFalse { apAliases.any { it.isPlainBase(Argument(1)) } } + assertTrue { apAliases.any { it.isPlainBase(Argument(2)) } } + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test conditional field write on argument receiver`() { + val method = findMethod(HEAP_SAMPLE, "conditionalFieldWrite") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertFalse { apAliases.any { it.isPlainBase(Argument(1)) } } + assertFalse { apAliases.any { it.isPlainBase(Argument(2)) } } + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test aliased receiver field write`() { + val method = findMethod(HEAP_SAMPLE, "aliasedReceiverFieldWrite") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == Argument(1) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test getter aliases this field`() { + val method = findMethod(INTERPROC_SAMPLE, "testGetterAlias") + val aa = aaForMethod(method, interProcParams(depth = 1)) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == AccessPathBase.This && it.accessors.singleFieldNamed(FIELD_INTERPROC) + } + } + } + + @Test + fun `test setter then getter`() { + val method = findMethod(INTERPROC_SAMPLE, "testSetterThenGetter") + val aa = aaForMethod(method, interProcParams(depth = 1)) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.base == Argument(0) } } + } + + @Test + fun `test identity same-class call`() { + val method = findMethod(INTERPROC_SAMPLE, "testIdentityCall") + val aa = aaForMethod(method, interProcParams(depth = 1)) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(0)) } } + } + + @Test + fun `test external call return is unknown`() { + val method = findMethod(INTERPROC_SAMPLE, "testExternalCallReturn") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertFalse { apAliases.any { it.isPlainBase(Argument(0)) } } + } + + @Test + fun `test external call invalidates heap aliases`() { + val method = findMethod(INTERPROC_SAMPLE, "testExternalCallInvalidatesHeap") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertFalse { apAliases.any { it.isPlainBase(Argument(0)) } } + } +} From 0ed9715abe357fa3646b077647ef5064f4d852a1 Mon Sep 17 00:00:00 2001 From: Daniil Logunov <43185213+DanielELog@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:20:35 +0300 Subject: [PATCH 02/12] Add tests for must alias analysis --- .../src/main/java/sample/AliasSettings.java | 5 + .../src/main/java/sample/BaseSample.java | 57 ++ .../sample/alias/CombinedHeapAliasSample.java | 74 ++ .../java/sample/alias/HeapAliasSample.java | 18 +- .../sample/alias/InterProcAliasSample.java | 23 +- .../java/sample/alias/LoopAliasSample.java | 11 +- .../jvm/ap/ifds/alias/DSUAliasAnalysis.kt | 15 +- .../jvm/ap/ifds/alias/JIRDSUAABuilder.kt | 11 +- .../opentaint/dataflow/jvm/BasicTestUtils.kt | 20 +- .../dataflow/jvm/DSUAliasAnalysisTestUtils.kt | 103 +++ ...est.kt => DSUMayAliasAnalysisStateTest.kt} | 86 +- .../alias/DSUMustAliasAnalysisStateTest.kt | 835 ++++++++++++++++++ .../jvm/ap/ifds/alias/MayAliasSampleTest.kt | 140 ++- .../ap/ifds/alias/MayAndMustRelationTest.kt | 60 ++ .../jvm/ap/ifds/alias/MustAliasSampleTest.kt | 142 ++- 15 files changed, 1456 insertions(+), 144 deletions(-) create mode 100644 core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/AliasSettings.java create mode 100644 core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/BaseSample.java create mode 100644 core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/CombinedHeapAliasSample.java create mode 100644 core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/DSUAliasAnalysisTestUtils.kt rename core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/{DSUAliasAnalysisStateTest.kt => DSUMayAliasAnalysisStateTest.kt} (87%) create mode 100644 core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUMustAliasAnalysisStateTest.kt create mode 100644 core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAndMustRelationTest.kt diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/AliasSettings.java b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/AliasSettings.java new file mode 100644 index 000000000..536655aa4 --- /dev/null +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/AliasSettings.java @@ -0,0 +1,5 @@ +package sample; + +public @interface AliasSettings { + int interProcDepth() default 0; +} diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/BaseSample.java b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/BaseSample.java new file mode 100644 index 000000000..350b2459e --- /dev/null +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/BaseSample.java @@ -0,0 +1,57 @@ +package sample; + +public class BaseSample { + + protected Object field; + + public Object getField() { + return this.field; + } + + public void setField(Object val) { + this.field = val; + } + + public static class Box { + public Object value; + + public void touchHeap() { } + } + + public static Object readValue(Box box) { + return box.value; + } + + public static Box makeBox(Object value) { + Box box = new Box(); + box.value = value; + return box; + } + + public static Box passThroughBox(Box box) { + return box; + } + + public static class Nested { + public Box box; + + public void touchHeap() { } + + public void touchHeapDepth2() { touchHeap(); } + } + + public static class Node { + public Node next; + public Object data; + } + + public static Object identity(Object x) { + return x; + } + + public static void sinkOneValue(Object v) { } + + public static void sinkTwoValues(Object v1, Object v2) { } + + public static void doNothing() { } +} diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/CombinedHeapAliasSample.java b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/CombinedHeapAliasSample.java new file mode 100644 index 000000000..9fd6a058c --- /dev/null +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/CombinedHeapAliasSample.java @@ -0,0 +1,74 @@ +package sample.alias; + +import sample.AliasSettings; +import sample.BaseSample; + +public class CombinedHeapAliasSample extends BaseSample { + + static void writeArgThenTouchHeap(Box box, Object src) { + box.value = src; + Object alias = box.value; + box.touchHeap(); + sinkOneValue(alias); + } + + @AliasSettings(interProcDepth = 1) + static void returnArgField(Box box) { + Object result = readValue(box); + sinkOneValue(result); + } + + @AliasSettings(interProcDepth = 1) + static void returnIdentityThenWriteField(Box box, Object src) { + Object tmp = identity(src); + box.value = tmp; + Object result = box.value; + sinkOneValue(result); + } + + @AliasSettings(interProcDepth = 1) + static void freshObjectCarriesReturnedArg(Object src) { + Box fresh = makeBox(identity(src)); + Object result = fresh.value; + sinkOneValue(result); + } + + static void freshObjectCopiesArgumentField(Box srcBox) { + Box fresh = new Box(); + fresh.value = srcBox.value; + Object result = fresh.value; + sinkOneValue(result); + } + + @AliasSettings(interProcDepth = 1) + static void passThroughReceiverThenReadField(Box box) { + Box alias = passThroughBox(box); + Object result = alias.value; + sinkOneValue(result); + } + + @AliasSettings(interProcDepth = 1) + static void nestedWriteReturnAndTouchHeap(Nested nested, Object src) { + nested.box.value = identity(src); + Object alias = readValue(nested.box); + nested.touchHeapDepth2(); + sinkOneValue(alias); + } + + static void overwriteFieldWithFreshObject(Box box, Object src) { + box.value = src; + Box fresh = new Box(); + fresh.value = new Object(); + box.value = fresh.value; + Object result = fresh.value; + sinkOneValue(result); + } + + @AliasSettings(interProcDepth = 1) + static void returnFreshBoxThenAliasField(Box box, Object src) { + box.value = src; + Box other = makeBox(box.value); + Object result = other.value; + sinkOneValue(result); + } +} diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/HeapAliasSample.java b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/HeapAliasSample.java index d37a7ca4a..c0ce5d40d 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/HeapAliasSample.java +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/HeapAliasSample.java @@ -1,19 +1,8 @@ package sample.alias; -public class HeapAliasSample { +import sample.BaseSample; - static class Box { - Object value; - } - - static class Nested { - Box box; - } - - static class Node { - Node next; - Object data; - } +public class HeapAliasSample extends BaseSample { static void readArgField(Box box) { Object dst = box.value; @@ -112,7 +101,4 @@ static void aliasedReceiverFieldWrite(Box b1, Box b2, Object src) { Object dst = b2.value; sinkOneValue(dst); } - - static void sinkOneValue(Object v) { } - static void sinkTwoValues(Object v1, Object v2) { } } diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/InterProcAliasSample.java b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/InterProcAliasSample.java index c5e89d351..9e6aee32c 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/InterProcAliasSample.java +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/InterProcAliasSample.java @@ -2,34 +2,25 @@ import java.util.Collections; import java.util.List; +import sample.AliasSettings; +import sample.BaseSample; -public class InterProcAliasSample { - - Object field; - - Object getField() { - return this.field; - } - - void setField(Object val) { - this.field = val; - } +public class InterProcAliasSample extends BaseSample { + @AliasSettings(interProcDepth = 1) void testGetterAlias() { Object result = getField(); sinkOneValue(result); } + @AliasSettings(interProcDepth = 1) void testSetterThenGetter(Object src) { setField(src); Object result = getField(); sinkOneValue(result); } - static Object identity(Object x) { - return x; - } - + @AliasSettings(interProcDepth = 1) static void testIdentityCall(Object src) { Object result = identity(src); sinkOneValue(result); @@ -47,6 +38,4 @@ static void testExternalCallInvalidatesHeap(Object src) { Object dst = arr[0]; sinkOneValue(dst); } - - static void sinkOneValue(Object v) { } } diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/LoopAliasSample.java b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/LoopAliasSample.java index 0aa8c8b6e..3544398f5 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/LoopAliasSample.java +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/samples/src/main/java/sample/alias/LoopAliasSample.java @@ -1,13 +1,9 @@ package sample.alias; import java.util.List; +import sample.BaseSample; -public class LoopAliasSample { - - static class Node { - Node next; - Object data; - } +public class LoopAliasSample extends BaseSample { static void aliasInLoop(Object a, Object b) { Object cur = a; @@ -62,7 +58,4 @@ static void nodeNextLoopData(Node head) { Object data = cur.data; sinkOneValue(data); } - - static void sinkOneValue(Object v) { } - static void doNothing() { } } diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt index aea507aed..b7f581e3f 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt @@ -23,6 +23,8 @@ class DSUAliasAnalysis( val mergeType: MergeType, val cancellation: AnalysisCancellation, ) { + private fun isMustAlias() = mergeType == MergeType.Must + private val aliasManager = AAInfoManager() private val dsuMergeStrategy = DsuMergeStrategy(aliasManager) @@ -217,13 +219,15 @@ class DSUAliasAnalysis( } else state if (stmt.cantMutateAliasedHeap()) return resultState + val instanceIndex = stmt.instance?.aliasInfo()?.index() + val argAliases = IntOpenHashSet() - stmt.args.forEach { arg -> + stmt.getUsedValues().forEach { arg -> val info = arg.aliasInfo() ?: return@forEach val infoIndex = aliasManager.getOrAdd(info) resultState.forEachAliasInSet(infoIndex) { argAliases.add(it) } } - return resultState.invalidateOuterHeapAliases(argAliases) + return resultState.invalidateOuterHeapAliases(argAliases, instanceIndex) } private fun evalCall( @@ -253,9 +257,12 @@ class DSUAliasAnalysis( return State.merge(aliasManager, dsuMergeStrategy, statesAfterCall, mergeType) } - private fun State.invalidateOuterHeapAliases(startInvalidAliases: IntOpenHashSet): State { + private fun State.invalidateOuterHeapAliases(startInvalidAliases: IntOpenHashSet, instance: Int?): State { val invalidAliases = collectTransitiveInvalidAliases(startInvalidAliases) + // keeping `this` aliases + instance?.let { invalidAliases.remove(it) } + val invalidHeapAliases = IntOpenHashSet() invalidAliases.forEach { val element = aliasManager.getElementUncheck(it) @@ -416,7 +423,7 @@ class DSUAliasAnalysis( val heapAlias = heapAppender(obj).index() var resultState = state - if (!state.containsMultipleConcreteOrOuterLocations(instanceInfo)) { + if (isMustAlias() || !state.containsMultipleConcreteOrOuterLocations(instanceInfo)) { resultState = resultState.remove(heapAlias) } diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt index e5c344ede..41bb94f3f 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt @@ -90,7 +90,16 @@ sealed interface Stmt : Comparable { sealed interface NoCall: Stmt - data class Call(val method: JIRMethod, val lValue: RefValue.Local?, val instance: Value?, val args: List, override val originalIdx: Int) : Stmt + data class Call(val method: JIRMethod, val lValue: RefValue.Local?, val instance: Value?, val args: List, override val originalIdx: Int) : Stmt { + private val usedValuesValue = lazy { usedValues() } + + private fun usedValues(): List { + if (instance == null) return args + return args + instance + } + + fun getUsedValues() = usedValuesValue.value + } data class Copy(val lValue: RefValue.Local, val rValue: RefValue, override val originalIdx: Int): NoCall data class Assign(val lValue: RefValue.Local, val expr: Expr, override val originalIdx: Int) : NoCall diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/BasicTestUtils.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/BasicTestUtils.kt index 961cf3bc6..df2c0e8f2 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/BasicTestUtils.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/BasicTestUtils.kt @@ -39,7 +39,7 @@ abstract class BasicTestUtils { protected lateinit var samplesJar: Path protected lateinit var db: JIRDatabase protected lateinit var cp: JIRClasspath - + protected val manager by lazy { JIRAnalysisManager(cp) } @BeforeAll @@ -82,10 +82,7 @@ abstract class BasicTestUtils { findClass(className).declaredMethods.find { it.name == methodName } ?: error("Method $methodName not found in $className") - protected fun aaForMethod( - method: JIRMethod, - params: JIRLocalAliasAnalysis.Params = JIRLocalAliasAnalysis.Params() - ): JIRLocalAliasAnalysis { + protected fun aaForMethod(method: JIRMethod): JIRLocalAliasAnalysis { val ep = method.instList.first() val usages = runBlocking { cp.usagesExt() } val graph = JApplicationGraphImpl(cp, usages) @@ -93,9 +90,18 @@ abstract class BasicTestUtils { val callResolver = JIRCallResolver(cp, SingleLocationUnit(method.enclosingClass.declaration.location)) val localReachability = JIRLocalVariableReachability(method, graph, manager) + val params = loadSettings(method) + return JIRLocalAliasAnalysis(ep, graph, callResolver, localReachability, manager, params) } + private fun loadSettings(method: JIRMethod): JIRLocalAliasAnalysis.Params { + val settings = method.annotations.find { it.name == ALIAS_SETTINGS } + val callDepth = settings?.values[INTER_PROC_SETTING] as? Int + ?: return JIRLocalAliasAnalysis.Params() + return interProcParams(callDepth) + } + protected fun interProcParams(depth: Int) = JIRLocalAliasAnalysis.Params(useAliasAnalysis = true, aliasAnalysisInterProcCallDepth = depth) @@ -142,8 +148,12 @@ abstract class BasicTestUtils { const val SIMPLE_SAMPLE = "$ALIAS_SAMPLE_PKG.SimpleAliasSample" const val LOOP_SAMPLE = "$ALIAS_SAMPLE_PKG.LoopAliasSample" const val HEAP_SAMPLE = "$ALIAS_SAMPLE_PKG.HeapAliasSample" + const val COMBINED_HEAP_SAMPLE = "$ALIAS_SAMPLE_PKG.CombinedHeapAliasSample" const val INTERPROC_SAMPLE = "$ALIAS_SAMPLE_PKG.InterProcAliasSample" + private const val ALIAS_SETTINGS = "sample.AliasSettings" + private const val INTER_PROC_SETTING = "interProcDepth" + protected const val FIELD_VALUE = "value" protected const val FIELD_BOX = "box" protected const val FIELD_NEXT = "next" diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/DSUAliasAnalysisTestUtils.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/DSUAliasAnalysisTestUtils.kt new file mode 100644 index 000000000..f7949cd82 --- /dev/null +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/DSUAliasAnalysisTestUtils.kt @@ -0,0 +1,103 @@ +package org.opentaint.dataflow.jvm + +import it.unimi.dsi.fastutil.ints.IntOpenHashSet +import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAccessor.Field +import org.opentaint.dataflow.jvm.ap.ifds.alias.AAInfo +import org.opentaint.dataflow.jvm.ap.ifds.alias.AAInfoManager +import org.opentaint.dataflow.jvm.ap.ifds.alias.ArrayAlias +import org.opentaint.dataflow.jvm.ap.ifds.alias.ContextInfo +import org.opentaint.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis +import org.opentaint.dataflow.jvm.ap.ifds.alias.FieldAlias +import org.opentaint.dataflow.jvm.ap.ifds.alias.HeapAlias +import org.opentaint.dataflow.jvm.ap.ifds.alias.LocalAlias +import org.opentaint.dataflow.jvm.ap.ifds.alias.LocalAlias.SimpleLoc +import org.opentaint.dataflow.jvm.ap.ifds.alias.MergeType +import org.opentaint.dataflow.jvm.ap.ifds.alias.RefValue +import org.opentaint.dataflow.jvm.ap.ifds.alias.State +import org.opentaint.dataflow.jvm.ap.ifds.alias.Stmt +import org.opentaint.dataflow.jvm.ap.ifds.alias.Unknown +import java.util.IdentityHashMap +import kotlin.collections.forEach + +abstract class DSUAliasAnalysisTestUtils(protected val mergeType: MergeType) { + + protected val manager = AAInfoManager() + protected val strategy = DSUAliasAnalysis.DsuMergeStrategy(manager) + + protected class StateBuilder( + private val manager: AAInfoManager, + private val strategy: DSUAliasAnalysis.DsuMergeStrategy, + private val mergeType: MergeType, + ) { + private var state = State.empty(manager, strategy) + + private val created = IdentityHashMap() + + fun local(idx: Int): LocalAlias = create( + SimpleLoc(RefValue.Local(idx, ContextInfo.rootContext)) + ) + + fun unknown(originalIdx: Int): Unknown = create( + Unknown(Stmt.Return(value = null, originalIdx = originalIdx), ContextInfo.rootContext) + ) + + fun arrayAlias(instanceInfo: AAInfo) = heapAlias(instanceInfo) { i -> HeapAlias(i, ArrayAlias) } + + fun fieldAlias(instanceInfo: AAInfo, fieldName: String) = heapAlias(instanceInfo) { i -> + HeapAlias(i, FieldAlias(Field("Cls", fieldName, "I"), isImmutable = true)) + } + + private fun heapAlias(instance: AAInfo, body: (Int) -> HeapAlias): HeapAlias { + val instanceId = infoId(instance) + val instanceGroupId = state.aliasGroupId(instanceId) + + return create(body(instanceGroupId)) + } + + private fun create(info: T): T { + created[info] = Unit + return info + } + + fun merge(set: Set) { + val setIds = infoIds(set) + state = state.mergeAliasSets(setIds) + } + + fun remove(set: Set) { + val setIds = infoIds(set) + state = state.removeUnsafe(setIds) + } + + private fun infoId(info: AAInfo): Int { + check(created.containsKey(info)) { "$info doesn't belongs to the current state" } + return manager.getOrAdd(info) + } + + private fun infoIds(set: Set): IntOpenHashSet { + val setIds = IntOpenHashSet() + set.forEach { setIds.add(infoId(it)) } + return setIds + } + + fun build(): State = state + + fun mergeStates(vararg builders: StateBuilder) { + val states = builders.map { it.state } + this.state = State.merge(manager, strategy, states, mergeType) + + builders.forEach { + created.putAll(it.created) + } + } + } + + protected inline fun buildState(body: StateBuilder.() -> Unit): State = + fillState(body).build() + + protected inline fun fillState(body: StateBuilder.() -> Unit): StateBuilder { + val builder = StateBuilder(manager, strategy, mergeType) + builder.body() + return builder + } +} diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysisStateTest.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUMayAliasAnalysisStateTest.kt similarity index 87% rename from core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysisStateTest.kt rename to core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUMayAliasAnalysisStateTest.kt index abb305b63..a8632bd55 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysisStateTest.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUMayAliasAnalysisStateTest.kt @@ -1,92 +1,10 @@ package org.opentaint.dataflow.jvm.ap.ifds.alias -import it.unimi.dsi.fastutil.ints.IntOpenHashSet -import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAccessor.Field -import org.opentaint.dataflow.jvm.ap.ifds.alias.LocalAlias.SimpleLoc -import java.util.IdentityHashMap +import org.opentaint.dataflow.jvm.DSUAliasAnalysisTestUtils import kotlin.test.Test import kotlin.test.assertEquals -class DSUAliasAnalysisStateTest { - - private val manager = AAInfoManager() - private val strategy = DSUAliasAnalysis.DsuMergeStrategy(manager) - - private class StateBuilder( - private val manager: AAInfoManager, - private val strategy: DSUAliasAnalysis.DsuMergeStrategy - ) { - private var state = State.empty(manager, strategy) - - private val created = IdentityHashMap() - - fun local(idx: Int): LocalAlias = create( - SimpleLoc(RefValue.Local(idx, ContextInfo.rootContext)) - ) - - fun unknown(originalIdx: Int): Unknown = create( - Unknown(Stmt.Return(value = null, originalIdx = originalIdx), ContextInfo.rootContext) - ) - - fun arrayAlias(instanceInfo: AAInfo) = heapAlias(instanceInfo) { i -> HeapAlias(i, ArrayAlias) } - - fun fieldAlias(instanceInfo: AAInfo, fieldName: String) = heapAlias(instanceInfo) { i -> - HeapAlias(i, FieldAlias(Field("Cls", fieldName, "I"), isImmutable = true)) - } - - private fun heapAlias(instance: AAInfo, body: (Int) -> HeapAlias): HeapAlias { - val instanceId = infoId(instance) - val instanceGroupId = state.aliasGroupId(instanceId) - - return create(body(instanceGroupId)) - } - - private fun create(info: T): T { - created[info] = Unit - return info - } - - fun merge(set: Set) { - val setIds = infoIds(set) - state = state.mergeAliasSets(setIds) - } - - fun remove(set: Set) { - val setIds = infoIds(set) - state = state.removeUnsafe(setIds) - } - - private fun infoId(info: AAInfo): Int { - check(created.containsKey(info)) { "$info doesn't belongs to the current state" } - return manager.getOrAdd(info) - } - - private fun infoIds(set: Set): IntOpenHashSet { - val setIds = IntOpenHashSet() - set.forEach { setIds.add(infoId(it)) } - return setIds - } - - fun build(): State = state - - fun mergeStates(vararg builders: StateBuilder) { - val states = builders.map { it.state } - this.state = State.merge(manager, strategy, states, MergeType.May) - - builders.forEach { - created.putAll(it.created) - } - } - } - - private inline fun buildState(body: StateBuilder.() -> Unit): State = - fillState(body).build() - - private inline fun fillState(body: StateBuilder.() -> Unit): StateBuilder { - val builder = StateBuilder(manager, strategy) - builder.body() - return builder - } +class DSUMayAliasAnalysisStateTest : DSUAliasAnalysisTestUtils(MergeType.May) { @Test fun mergeAliasSetsOfTwoLocals() { diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUMustAliasAnalysisStateTest.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUMustAliasAnalysisStateTest.kt new file mode 100644 index 000000000..6e2252416 --- /dev/null +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUMustAliasAnalysisStateTest.kt @@ -0,0 +1,835 @@ +package org.opentaint.dataflow.jvm.ap.ifds.alias + +import org.opentaint.dataflow.jvm.DSUAliasAnalysisTestUtils +import kotlin.test.Test +import kotlin.test.assertEquals + +class DSUMustAliasAnalysisStateTest : DSUAliasAnalysisTestUtils(MergeType.Must) { + + @Test + fun mergeAliasSetsOfTwoLocals() { + val set1 = buildState { + merge(setOf(local(0), local(1))) + merge(setOf(local(0), local(1))) + } + + val set2 = buildState { + merge(setOf(local(0), local(1))) + } + + assertEquals(set2, set1) + } + + @Test + fun mergeAliasSetsOfThreeElements() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + merge(setOf(a)) + merge(setOf(b)) + merge(setOf(c)) + merge(setOf(a, b, c)) + } + + val set2 = buildState { + merge(setOf(local(0), local(1), local(2))) + } + + assertEquals(set2, set1) + } + + @Test + fun mergeAliasSetsWithSingleElementIsNoop() { + val set1 = buildState { + val a = local(0) + val b = local(1) + merge(setOf(a)) + merge(setOf(b)) + merge(setOf(a)) + } + + val set2 = buildState { + merge(setOf(local(0))) + merge(setOf(local(1))) + } + + assertEquals(set2, set1) + } + + @Test + fun mergeAliasSetsPreservesDisjointGroups() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + val d = local(3) + merge(setOf(a)) + merge(setOf(b)) + merge(setOf(c)) + merge(setOf(d)) + merge(setOf(a, b)) + } + + val set2 = buildState { + merge(setOf(local(0), local(1))) + merge(setOf(local(2))) + merge(setOf(local(3))) + } + + assertEquals(set2, set1) + } + + @Test + fun removeUnsafeSingleElement() { + val set1 = buildState { + val a = local(0) + val b = local(1) + merge(setOf(a, b)) + remove(setOf(a)) + } + + val set2 = buildState { + merge(setOf(local(1))) + } + + assertEquals(set2, set1) + } + + @Test + fun removeUnsafeEmptySetIsNoop() { + val set1 = buildState { + val a = local(0) + val b = local(1) + merge(setOf(a, b)) + remove(emptySet()) + } + + val set2 = buildState { + merge(setOf(local(0), local(1))) + } + + assertEquals(set2, set1) + } + + @Test + fun removeUnsafeAllElements() { + val set1 = buildState { + val a = local(0) + val b = local(1) + merge(setOf(a, b)) + remove(setOf(a, b)) + } + + val set2 = buildState {} + + assertEquals(set2, set1) + } + + @Test + fun removeUnsafeFromMultipleGroups() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + val d = local(3) + merge(setOf(a, b)) + merge(setOf(c, d)) + remove(setOf(a, c)) + } + + val set2 = buildState { + merge(setOf(local(1))) + merge(setOf(local(3))) + } + + assertEquals(set2, set1) + } + + @Test + fun mergeTwoDisjointStates() { + val set1 = buildState { + mergeStates( + fillState { merge(setOf(local(0), local(1))) }, + fillState { merge(setOf(local(2), local(3))) }, + ) + } + + val set2 = buildState {} + + assertEquals(set2, set1) + } + + @Test + fun mergeOverlappingStates() { + val set1 = buildState { + mergeStates( + fillState { merge(setOf(local(0), local(1))) }, + fillState { merge(setOf(local(1), local(2))) }, + ) + } + + val set2 = buildState {} + + assertEquals(set2, set1) + } + + @Test + fun mergeSingleState() { + val set1 = buildState { + mergeStates( + fillState { merge(setOf(local(0), local(1))) }, + ) + } + + val set2 = buildState { + merge(setOf(local(0), local(1))) + } + + assertEquals(set2, set1) + } + + @Test + fun mergeAliasSetsAfterRemoveUnsafe() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + val d = local(3) + merge(setOf(a, b)) + merge(setOf(c, d)) + remove(setOf(b)) + merge(setOf(a, c)) + } + + val set2 = buildState { + merge(setOf(local(0), local(2), local(3))) + } + + assertEquals(set2, set1) + } + + @Test + fun removeUnsafeAfterMergeAliasSets() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + merge(setOf(a)) + merge(setOf(b)) + merge(setOf(c)) + merge(setOf(a, b, c)) + remove(setOf(b)) + } + + val set2 = buildState { + merge(setOf(local(0), local(2))) + } + + assertEquals(set2, set1) + } + + @Test + fun mergeStatesFollowedByMergeAliasSets() { + val set1 = buildState { + mergeStates( + fillState { merge(setOf(local(0), local(1))) }, + fillState { merge(setOf(local(2), local(3))) }, + ) + merge(setOf(local(1), local(2))) + } + + val set2 = buildState { + merge(setOf(local(1), local(2))) + } + + assertEquals(set2, set1) + } + + @Test + fun mergeStatesFollowedByRemoveUnsafe() { + val set1 = buildState { + mergeStates( + fillState { merge(setOf(local(0), local(1))) }, + fillState { merge(setOf(local(2), local(3))) }, + ) + remove(setOf(local(0), local(3))) + } + + val set2 = buildState { + merge(setOf(local(1))) + merge(setOf(local(2))) + } + + assertEquals(set2, set1) + } + + @Test + fun mergeAliasSetsWithHeapAliases() { + val set1 = buildState { + val loc = local(0) + val arr = arrayAlias(loc) + val c = local(5) + merge(setOf(loc, arr)) + merge(setOf(c)) + merge(setOf(loc, c)) + } + + val set2 = buildState { + val loc = local(0) + val arr = arrayAlias(loc) + merge(setOf(loc, arr, local(5))) + } + + assertEquals(set2, set1) + } + + @Test + fun removeUnsafeWithFieldAliases() { + val set1 = buildState { + val loc = local(0) + val f = fieldAlias(loc, "x") + val c = local(1) + merge(setOf(loc, f, c)) + remove(setOf(f)) + } + + val set2 = buildState { + merge(setOf(local(0), local(1))) + } + + assertEquals(set2, set1) + } + + @Test + fun mergeStatesWithUnknownAliases() { + val set1 = buildState { + mergeStates( + fillState { merge(setOf(local(0), unknown(0), unknown(1))) }, + fillState { merge(setOf(local(1), unknown(0), unknown(1))) }, + ) + } + + val set2 = buildState { + merge(setOf(unknown(0), unknown(1))) + } + + assertEquals(set2, set1) + } + + @Test + fun chainedMergeRemoveMerge() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + val d = local(3) + val e = local(4) + merge(setOf(a)) + merge(setOf(b)) + merge(setOf(c)) + merge(setOf(d)) + merge(setOf(e)) + merge(setOf(a, b)) + remove(setOf(c)) + merge(setOf(d, e)) + } + + val set2 = buildState { + merge(setOf(local(0), local(1))) + merge(setOf(local(3), local(4))) + } + + assertEquals(set2, set1) + } + + @Test + fun mergeThreeOverlappingStates() { + val set1 = buildState { + mergeStates( + fillState { merge(setOf(local(0), local(1), local(2), local(4))) }, + fillState { merge(setOf(local(0), local(1), local(2), local(5))) }, + fillState { merge(setOf(local(0), local(1), local(2), local(3))) }, + ) + } + + val set2 = buildState { + merge(setOf(local(0), local(1), local(2))) + } + + assertEquals(set2, set1) + } + + @Test + fun mergeStatesRemoveAndMergeAliasSets() { + val set1 = buildState { + mergeStates( + fillState { + merge(setOf(local(0), local(2))) + merge(setOf(local(1))) + }, + fillState { merge(setOf(local(0), local(1), local(2))) }, + ) + remove(setOf(local(2))) + merge(setOf(local(0), local(3))) + } + + val set2 = buildState { + merge(setOf(local(0), local(3))) + } + + assertEquals(set2, set1) + } + + @Test + fun mergeEmptyStates() { + val set1 = buildState { + mergeStates( + fillState {}, + fillState {}, + ) + } + + val set2 = buildState {} + + assertEquals(set2, set1) + } + + @Test + fun mergeAliasSetsOnEmptySetIsNoop() { + val set1 = buildState { + val a = local(0) + val b = local(1) + merge(setOf(a)) + merge(setOf(b)) + merge(emptySet()) + } + + val set2 = buildState { + merge(setOf(local(0))) + merge(setOf(local(1))) + } + + assertEquals(set2, set1) + } + + @Test + fun removeUnsafeThenMergeThenRemove() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + val d = local(3) + val e = local(4) + merge(setOf(a, b)) + merge(setOf(c)) + merge(setOf(d)) + merge(setOf(e)) + remove(setOf(a)) + merge(setOf(b, c)) + remove(setOf(d)) + } + + val set2 = buildState { + merge(setOf(local(1), local(2))) + merge(setOf(local(4))) + } + + assertEquals(set2, set1) + } + + @Test + fun deepHeapChainMerge() { + val set1 = buildState { + val loc = local(10) + val d1 = arrayAlias(loc) + val d2 = arrayAlias(d1) + val d3 = arrayAlias(d2) + merge(setOf(loc, d1)) + merge(setOf(d2, d3)) + merge(setOf(loc, d2)) + } + + val set2 = buildState { + val loc = local(10) + val d1 = arrayAlias(loc) + val d2 = arrayAlias(d1) + val d3 = arrayAlias(d2) + merge(setOf(loc, d1, d2, d3)) + } + + assertEquals(set2, set1) + } + + @Test + fun deepMixedHeapChainMergeAndRemove() { + val set1 = buildState { + val loc = local(30) + val arr1 = arrayAlias(loc) + val fld2 = fieldAlias(arr1, "f") + val arr3 = arrayAlias(fld2) + val e = local(99) + merge(setOf(loc, arr1)) + merge(setOf(fld2, arr3)) + merge(setOf(e)) + merge(setOf(arr1, fld2)) + remove(setOf(e)) + } + + val set2 = buildState { + val loc = local(30) + val arr1 = arrayAlias(loc) + val fld2 = fieldAlias(arr1, "f") + val arr3 = arrayAlias(fld2) + merge(setOf(loc, arr1, fld2, arr3)) + } + + assertEquals(set2, set1) + } + + @Test + fun deepHeapChainStateMergeConnects() { + val set1 = buildState { + mergeStates( + fillState { + val loc = local(40) + val h1 = arrayAlias(loc) + merge(setOf(loc, h1)) + }, + fillState { + val loc = local(40) + val h1 = arrayAlias(loc) + val h2 = arrayAlias(h1) + val h3 = arrayAlias(h2) + merge(setOf(loc, h1, h2, h3)) + }, + ) + } + + val set2 = buildState { + val loc = local(40) + val h1 = arrayAlias(loc) + merge(setOf(loc, h1)) + } + + assertEquals(set2, set1) + } + + @Test + fun equalityAfterDifferentMergeOrder() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + val d = local(3) + merge(setOf(a)) + merge(setOf(b)) + merge(setOf(c)) + merge(setOf(d)) + merge(setOf(a, b)) + merge(setOf(c, d)) + } + + val set2 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + val d = local(3) + merge(setOf(a)) + merge(setOf(b)) + merge(setOf(c)) + merge(setOf(d)) + merge(setOf(c, d)) + merge(setOf(a, b)) + } + + assertEquals(set1, set2) + } + + @Test + fun equalityMergeAllAtOnceVsIncrementally() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + merge(setOf(a)) + merge(setOf(b)) + merge(setOf(c)) + merge(setOf(a, b, c)) + } + + val set2 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + merge(setOf(a)) + merge(setOf(b)) + merge(setOf(c)) + merge(setOf(a, b)) + merge(setOf(b, c)) + } + + assertEquals(set1, set2) + } + + @Test + fun equalityRemoveThenMergeVsMergeFiltered() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + val d = local(3) + merge(setOf(a, b)) + merge(setOf(c, d)) + remove(setOf(b, d)) + merge(setOf(a, c)) + } + + val set2 = buildState { + merge(setOf(local(0), local(2))) + } + + assertEquals(set1, set2) + } + + @Test + fun equalityStateMergeVsManualMergeAliasSets() { + val set1 = buildState { + mergeStates( + fillState { merge(setOf(local(0), local(1), local(2), local(3))) }, + fillState { merge(setOf(local(0), local(1), local(2), local(4))) }, + ) + } + + val set2 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + merge(setOf(a)) + merge(setOf(b)) + merge(setOf(c)) + merge(setOf(a, b)) + merge(setOf(b, c)) + } + + assertEquals(set1, set2) + } + + @Test + fun equalityMergeTwoWaysWithDeepHeap() { + val set1 = buildState { + mergeStates( + fillState { + val loc = local(90) + val h1 = arrayAlias(loc) + val h2 = fieldAlias(h1, "q") + val h3 = arrayAlias(h2) + val h4 = arrayAlias(h3) + merge(setOf(loc, h1, h2, h3, h4, local(92))) + }, + fillState { + val loc = local(90) + val h1 = arrayAlias(loc) + val h2 = fieldAlias(h1, "q") + val h3 = arrayAlias(h2) + val h5 = fieldAlias(h3, "q") + merge(setOf(loc, h1, h2, h3, h5, local(91))) + }, + ) + } + + val set2 = buildState { + val loc = local(90) + val h1 = arrayAlias(loc) + val h2 = fieldAlias(h1, "q") + val h3 = arrayAlias(h2) + merge(setOf(loc, h1, h2, h3)) + } + + assertEquals(set1, set2) + } + + @Test + fun equalityChainedOpsVsDirectConstruction() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + val d = local(3) + val e = local(4) + val f = local(5) + merge(setOf(a)) + merge(setOf(b)) + merge(setOf(c)) + merge(setOf(d)) + merge(setOf(e)) + merge(setOf(f)) + merge(setOf(a, b, c)) + remove(setOf(d)) + merge(setOf(e, f)) + } + + val set2 = buildState { + merge(setOf(local(0), local(1), local(2))) + merge(setOf(local(4), local(5))) + } + + assertEquals(set1, set2) + } + + @Test + fun equalityMutableCopyChainVsOriginalChain() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + merge(setOf(a)) + merge(setOf(b)) + merge(setOf(c)) + merge(setOf(a, b)) + remove(setOf(c)) + } + + val set2 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + merge(setOf(a)) + merge(setOf(b)) + merge(setOf(c)) + merge(setOf(a, b)) + remove(setOf(c)) + } + + assertEquals(set1, set2) + } + + @Test + fun equalityRemoveOrderIndependence() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + val d = local(3) + merge(setOf(a, b, c, d)) + remove(setOf(a)) + remove(setOf(c)) + } + + val set2 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + val d = local(3) + merge(setOf(a, b, c, d)) + remove(setOf(c)) + remove(setOf(a)) + } + + assertEquals(set1, set2) + } + + @Test + fun equalityRemoveAtOnceVsOneByOne() { + val set1 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + val d = local(3) + merge(setOf(a, b, c, d)) + remove(setOf(a, c)) + } + + val set2 = buildState { + val a = local(0) + val b = local(1) + val c = local(2) + val d = local(3) + merge(setOf(a, b, c, d)) + remove(setOf(a)) + remove(setOf(c)) + } + + assertEquals(set1, set2) + } + + @Test + fun equalityDeepChainMergeStateThenRemoveVsBuildDirect() { + val set1 = buildState { + mergeStates( + fillState { + val loc = local(140) + val f1 = fieldAlias(loc, "a") + val f2 = fieldAlias(f1, "b") + val f3 = fieldAlias(f2, "c") + merge(setOf(loc, f1, f2, f3, local(141))) + }, + fillState { + val loc = local(140) + val f1 = fieldAlias(loc, "a") + val f2 = fieldAlias(f1, "b") + val f3 = fieldAlias(f2, "c") + merge(setOf(loc, f1, f2, f3, local(141))) + }, + ) + remove(setOf(local(141))) + } + + val set2 = buildState { + val loc = local(140) + val f1 = fieldAlias(loc, "a") + val f2 = fieldAlias(f1, "b") + val f3 = fieldAlias(f2, "c") + merge(setOf(loc, f1, f2, f3)) + } + + assertEquals(set1, set2) + } + + @Test + fun equalityThreeStateMergeVsTwoStepMerge() { + val set1 = buildState { + mergeStates( + fillState { merge(setOf(local(0), local(1))) }, + fillState { merge(setOf(local(1), local(2))) }, + fillState { merge(setOf(local(2), local(3))) }, + ) + } + + val set2 = buildState { + val merged12 = fillState { + mergeStates( + fillState { merge(setOf(local(0), local(1))) }, + fillState { merge(setOf(local(1), local(2))) }, + ) + } + mergeStates( + merged12, + fillState { merge(setOf(local(2), local(3))) }, + ) + } + + assertEquals(set1, set2) + } + + @Test + fun equalityMergeOrderDoesNotMatter() { + val set1 = buildState { + mergeStates( + fillState { merge(setOf(local(0), local(1))) }, + fillState { merge(setOf(local(2), local(3))) }, + fillState { merge(setOf(local(1), local(2))) }, + ) + } + + val set2 = buildState { + mergeStates( + fillState { merge(setOf(local(1), local(2))) }, + fillState { merge(setOf(local(0), local(1))) }, + fillState { merge(setOf(local(2), local(3))) }, + ) + } + + assertEquals(set1, set2) + } +} diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAliasSampleTest.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAliasSampleTest.kt index 1935426b0..babc2e3c3 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAliasSampleTest.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAliasSampleTest.kt @@ -402,7 +402,7 @@ class MayAliasSampleTest : BasicTestUtils() { @Test fun `test getter aliases this field`() { val method = findMethod(INTERPROC_SAMPLE, "testGetterAlias") - val aa = aaForMethod(method, interProcParams(depth = 1)) + val aa = aaForMethod(method) val sink = method.findSinkCall("sinkOneValue") val apAliases = aa.sinkArgApAliases(sink) @@ -417,7 +417,7 @@ class MayAliasSampleTest : BasicTestUtils() { @Test fun `test setter then getter`() { val method = findMethod(INTERPROC_SAMPLE, "testSetterThenGetter") - val aa = aaForMethod(method, interProcParams(depth = 1)) + val aa = aaForMethod(method) val sink = method.findSinkCall("sinkOneValue") val apAliases = aa.sinkArgApAliases(sink) @@ -428,7 +428,7 @@ class MayAliasSampleTest : BasicTestUtils() { @Test fun `test identity same-class call`() { val method = findMethod(INTERPROC_SAMPLE, "testIdentityCall") - val aa = aaForMethod(method, interProcParams(depth = 1)) + val aa = aaForMethod(method) val sink = method.findSinkCall("sinkOneValue") val apAliases = aa.sinkArgApAliases(sink) @@ -457,4 +457,138 @@ class MayAliasSampleTest : BasicTestUtils() { assertFalse { apAliases.any { it.isPlainBase(Argument(0)) } } } + + @Test + fun `test combined write arg then touch heap`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "writeArgThenTouchHeap") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(1)) } } + assertFalse { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test combined return argument field`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "returnArgField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test combined return identity then write field`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "returnIdentityThenWriteField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(1)) } } + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test combined fresh object carries returned arg`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "freshObjectCarriesReturnedArg") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(0)) } } + } + + @Test + fun `test combined fresh object copies argument field`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "freshObjectCopiesArgumentField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test combined pass through receiver then read field`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "passThroughReceiverThenReadField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test combined nested write return and touch heap`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "nestedWriteReturnAndTouchHeap") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(1)) } } + assertFalse { apAliases.any { it.accessors.isNotEmpty() } } + } + + @Test + fun `test combined overwrite field with fresh object`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "overwriteFieldWithFreshObject") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + // note: may want to remove this alias link for may analysis in the future + assertTrue { apAliases.any { it.isPlainBase(Argument(1)) } } + + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test combined return fresh box then alias field`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "returnFreshBoxThenAliasField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(1)) } } + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } } diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAndMustRelationTest.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAndMustRelationTest.kt new file mode 100644 index 000000000..edbfcab4a --- /dev/null +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAndMustRelationTest.kt @@ -0,0 +1,60 @@ +package org.opentaint.dataflow.jvm.ap.ifds.alias + +import org.junit.jupiter.api.TestInstance +import org.opentaint.dataflow.ap.ifds.AccessPathBase +import org.opentaint.dataflow.ap.ifds.AccessPathBase.Companion.Argument +import org.opentaint.dataflow.jvm.BasicTestUtils +import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis +import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAccessor +import org.opentaint.ir.api.jvm.JIRMethod +import org.opentaint.ir.api.jvm.cfg.JIRCallInst +import org.opentaint.ir.api.jvm.cfg.JIRInst +import org.opentaint.ir.api.jvm.cfg.JIRLocalVar +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import kotlin.test.fail + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class MayAndMustRelationTest : BasicTestUtils() { + override fun JIRLocalAliasAnalysis.getAliases( + base: AccessPathBase.LocalVar, + statement: JIRInst + ): List = error("unreachable") + + @Test + fun `check must alias inclusion in may alias`() { + val allClasses = cp.locations.flatMap { it.classNames.orEmpty() } + val sampleClasses = allClasses.filter { it.startsWith(ALIAS_SAMPLE_PKG) } + val methods = sampleClasses.flatMap { findClass(it).declaredMethods } + + methods.forEach { method -> + val aa = aaForMethod(method) + val sink = method.findOneOfSinkCalls() ?: return@forEach + + sink.callExpr.args.filterIsInstance().forEach { arg -> + val simpleLoc = AccessPathBase.LocalVar(arg.index) + + val mayResult = aa.findAlias(simpleLoc, sink).orEmpty().toSet() + val mustResult = aa.findMustAlias(simpleLoc, sink).orEmpty() + + mustResult.forEach { alias -> + if (alias !in mayResult) { + fail("Must alias diverged with May at `${method.enclosingClass.name}.${method.name}`!") + } + } + } + } + } + + private fun JIRMethod.findOneOfSinkCalls(): JIRCallInst? = + instList.filterIsInstance().firstOrNull { it.callExpr.method.name in SINKS } + + companion object { + private val SINKS = listOf( + "sinkOneValue", + "sinkTwoValues", + "testSimpleArgAlias", + ) + } +} diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MustAliasSampleTest.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MustAliasSampleTest.kt index 06b8050dd..a1d04f095 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MustAliasSampleTest.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MustAliasSampleTest.kt @@ -245,14 +245,14 @@ class MustAliasSampleTest : BasicTestUtils() { it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) } } - assertTrue { + assertFalse { aValueAliases.any { it.base == Argument(1) && it.accessors.singleFieldNamed(FIELD_VALUE) } } val bValueAliases = aa.valueApAliases(sink.callExpr.args[1], sink) - assertTrue { + assertFalse { bValueAliases.any { it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) } @@ -399,7 +399,7 @@ class MustAliasSampleTest : BasicTestUtils() { @Test fun `test getter aliases this field`() { val method = findMethod(INTERPROC_SAMPLE, "testGetterAlias") - val aa = aaForMethod(method, interProcParams(depth = 1)) + val aa = aaForMethod(method) val sink = method.findSinkCall("sinkOneValue") val apAliases = aa.sinkArgApAliases(sink) @@ -414,7 +414,7 @@ class MustAliasSampleTest : BasicTestUtils() { @Test fun `test setter then getter`() { val method = findMethod(INTERPROC_SAMPLE, "testSetterThenGetter") - val aa = aaForMethod(method, interProcParams(depth = 1)) + val aa = aaForMethod(method) val sink = method.findSinkCall("sinkOneValue") val apAliases = aa.sinkArgApAliases(sink) @@ -425,7 +425,7 @@ class MustAliasSampleTest : BasicTestUtils() { @Test fun `test identity same-class call`() { val method = findMethod(INTERPROC_SAMPLE, "testIdentityCall") - val aa = aaForMethod(method, interProcParams(depth = 1)) + val aa = aaForMethod(method) val sink = method.findSinkCall("sinkOneValue") val apAliases = aa.sinkArgApAliases(sink) @@ -454,4 +454,136 @@ class MustAliasSampleTest : BasicTestUtils() { assertFalse { apAliases.any { it.isPlainBase(Argument(0)) } } } + + @Test + fun `test combined write arg then touch heap`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "writeArgThenTouchHeap") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(1)) } } + assertFalse { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test combined return argument field`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "returnArgField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test combined return identity then write field`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "returnIdentityThenWriteField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(1)) } } + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test combined fresh object carries returned arg`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "freshObjectCarriesReturnedArg") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(0)) } } + } + + @Test + fun `test combined fresh object copies argument field`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "freshObjectCopiesArgumentField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test combined pass through receiver then read field`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "passThroughReceiverThenReadField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test combined nested write return and touch heap`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "nestedWriteReturnAndTouchHeap") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(1)) } } + assertFalse { apAliases.any { it.accessors.isNotEmpty() } } + } + + @Test + fun `test combined overwrite field with fresh object`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "overwriteFieldWithFreshObject") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertFalse { apAliases.any { it.isPlainBase(Argument(1)) } } + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } + + @Test + fun `test combined return fresh box then alias field`() { + val method = findMethod(COMBINED_HEAP_SAMPLE, "returnFreshBoxThenAliasField") + val aa = aaForMethod(method) + + val sink = method.findSinkCall("sinkOneValue") + val apAliases = aa.sinkArgApAliases(sink) + + assertTrue { apAliases.any { it.isPlainBase(Argument(1)) } } + assertTrue { + apAliases.any { + it.base == Argument(0) && it.accessors.singleFieldNamed(FIELD_VALUE) + } + } + } } From f10e85bf4c7d79b2f635d47a6a3a87362b1311e5 Mon Sep 17 00:00:00 2001 From: Daniil Logunov <43185213+DanielELog@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:45:08 +0300 Subject: [PATCH 03/12] Fix unused imports --- .../dataflow/jvm/ap/ifds/alias/MayAndMustRelationTest.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAndMustRelationTest.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAndMustRelationTest.kt index edbfcab4a..6b338d2ae 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAndMustRelationTest.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/MayAndMustRelationTest.kt @@ -2,17 +2,13 @@ package org.opentaint.dataflow.jvm.ap.ifds.alias import org.junit.jupiter.api.TestInstance import org.opentaint.dataflow.ap.ifds.AccessPathBase -import org.opentaint.dataflow.ap.ifds.AccessPathBase.Companion.Argument import org.opentaint.dataflow.jvm.BasicTestUtils import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis -import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAccessor import org.opentaint.ir.api.jvm.JIRMethod import org.opentaint.ir.api.jvm.cfg.JIRCallInst import org.opentaint.ir.api.jvm.cfg.JIRInst import org.opentaint.ir.api.jvm.cfg.JIRLocalVar import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue import kotlin.test.fail @TestInstance(TestInstance.Lifecycle.PER_CLASS) From 868af6e9484fe572a3c2e4ff1b34d513d6751528 Mon Sep 17 00:00:00 2001 From: Daniil Logunov <43185213+DanielELog@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:29:24 +0300 Subject: [PATCH 04/12] Finalize must alias addition --- .../jvm/ap/ifds/JIRLocalAliasAnalysis.kt | 51 +++++- .../ifds/alias/JIRIntraProcAliasAnalysis.kt | 93 ++++++++++- .../jvm/ap/ifds/analysis/FactUtils.kt | 150 ++++++++++++++++++ .../jvm/ap/ifds/analysis/JIRAliasUtil.kt | 9 ++ .../analysis/JIRMethodCallFlowFunction.kt | 24 ++- .../analysis/JIRMethodCallSummaryHandler.kt | 19 +++ .../analysis/JIRMethodSequentFlowFunction.kt | 36 ++++- 7 files changed, 362 insertions(+), 20 deletions(-) create mode 100644 core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/FactUtils.kt diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt index e150be7ee..4239ff8c5 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt @@ -1,9 +1,9 @@ package org.opentaint.dataflow.jvm.ap.ifds import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import org.opentaint.dataflow.ap.ifds.AccessPathBase import org.opentaint.dataflow.jvm.ap.ifds.alias.JIRIntraProcAliasAnalysis -import org.opentaint.dataflow.jvm.ap.ifds.alias.MergeType import org.opentaint.ir.api.common.cfg.CommonInst import org.opentaint.ir.api.jvm.cfg.JIRInst import org.opentaint.jvm.graph.JApplicationGraph @@ -24,14 +24,19 @@ class JIRLocalAliasAnalysis( val aliasAnalysisTimeLimit: Duration = 10.seconds, ) - private val mayAliasInfo by lazy { compute(MergeType.May) } - private val mustAliasInfo by lazy { compute(MergeType.Must) } + private val mayAliasInfo by lazy { computeMay() } + private val mustAliasInfo by lazy { computeMust() } class MethodAliasInfo( val aliasBeforeStatement: Array>?>?, val aliasAfterStatement: Array>?>?, ) + class MethodMustAliasInfo( + val aliasBeforeStatement: Array>?>, + val aliasAfterStatement: Array>?>, + ) + private fun getLocalVarAliases( alias: Array>?>, instIdx: Int, base: AccessPathBase.LocalVar @@ -40,9 +45,17 @@ class JIRLocalAliasAnalysis( it !is AliasApInfo || it.accessors.isNotEmpty() || it.base != base }?.map { it.wrapAliasInfo() } - fun findMustAlias(base: AccessPathBase.LocalVar, statement: CommonInst): List? { + private fun getAccessPathBaseAliases( + alias: Array>?>, + instIdx: Int, base: AccessPathBase + ): List? = + alias[instIdx]?.getOrDefault(base, null)?.filter { + it !is AliasApInfo || it.accessors.isNotEmpty() || it.base != base + }?.map { it.wrapAliasInfo() } + + fun findMustAlias(base: AccessPathBase, statement: CommonInst): List? { val idx = languageManager.getInstIndex(statement) - return getLocalVarAliases(mustAliasInfo.aliasBeforeStatement, idx, base) + return getAccessPathBaseAliases(mustAliasInfo.aliasBeforeStatement, idx, base) } fun findAlias(base: AccessPathBase.LocalVar, statement: CommonInst): List? { @@ -63,8 +76,12 @@ class JIRLocalAliasAnalysis( return getLocalVarAliases(aliasAfter, idx, base) } - private fun compute(mergeType: MergeType): MethodAliasInfo { - return JIRIntraProcAliasAnalysis(entryPoint, graph, callResolver, languageManager, params, mergeType).compute(localVariableReachability) + private fun computeMay(): MethodAliasInfo { + return JIRIntraProcAliasAnalysis(entryPoint, graph, callResolver, languageManager, params).computeMay(localVariableReachability) + } + + private fun computeMust(): MethodMustAliasInfo { + return JIRIntraProcAliasAnalysis(entryPoint, graph, callResolver, languageManager, params).computeMust(localVariableReachability) } sealed interface AliasAccessor { @@ -98,6 +115,14 @@ class JIRLocalAliasAnalysis( return result } + fun wrapAllInfo(info: Object2ObjectOpenHashMap>): Object2ObjectOpenHashMap> { + val result = Object2ObjectOpenHashMap>() + for ((key, aliases) in info) { + result.put(key, List(aliases.size) { aliases[it].wrapAliasInfo() }) + } + return result + } + fun unwrapAllInfo(info: Int2ObjectOpenHashMap>): Int2ObjectOpenHashMap> { val result = Int2ObjectOpenHashMap>(info.size, 0.99f) val iter = info.int2ObjectEntrySet().fastIterator() @@ -109,5 +134,17 @@ class JIRLocalAliasAnalysis( } return result } + + fun unwrapAllInfo(info: Object2ObjectOpenHashMap>): Object2ObjectOpenHashMap> { + val result = Object2ObjectOpenHashMap>(info.size, 0.99f) + val iter = info.object2ObjectEntrySet().fastIterator() + while (iter.hasNext()) { + val entry = iter.next() + val value = entry.value + val unwrapped = Array(value.size) { value[it].unwrap() } + result.put(entry.key, unwrapped) + } + return result + } } } diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt index afe90328f..6ebab9f56 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt @@ -1,6 +1,7 @@ package org.opentaint.dataflow.jvm.ap.ifds.alias import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import mu.KLogging import org.opentaint.dataflow.ap.ifds.AccessPathBase import org.opentaint.dataflow.graph.CompactGraph @@ -28,7 +29,6 @@ class JIRIntraProcAliasAnalysis( private val callResolver: JIRCallResolver, private val languageManager: JIRLanguageManager, private val params: JIRLocalAliasAnalysis.Params, - private val mergeType: MergeType, ) { companion object { private val logger = object : KLogging() {}.logger @@ -60,7 +60,7 @@ class JIRIntraProcAliasAnalysis( override fun buildMethodJig(entryPoint: JIRInst): JIRInstGraph = getJIG(entryPoint) } - fun compute( + fun computeMay( localVariableReachability: JIRLocalVariableReachability ): JIRLocalAliasAnalysis.MethodAliasInfo = withAnalysisCancellation( @@ -83,7 +83,7 @@ class JIRIntraProcAliasAnalysis( localVariableReachability: JIRLocalVariableReachability ): JIRLocalAliasAnalysis.MethodAliasInfo { val jig = getJIG(entryPoint) - val daa = DSUAliasAnalysis(CallResolver(), localVariableReachability, mergeType, cancellation).analyze(jig) + val daa = DSUAliasAnalysis(CallResolver(), localVariableReachability, MergeType.May, cancellation).analyze(jig) val aliasBeforeStatement = Array(jig.statements.size) { i -> resolveLocalVar(daa.statesBeforeStmt[i], localVariableReachability, i) @@ -96,6 +96,21 @@ class JIRIntraProcAliasAnalysis( return compressAliasInfo(aliasBeforeStatement, aliasAfterStatement) } + fun computeMust(localVariableReachability: JIRLocalVariableReachability): JIRLocalAliasAnalysis.MethodMustAliasInfo { + val jig = getJIG(entryPoint) + val daa = DSUAliasAnalysis(CallResolver(), localVariableReachability, MergeType.Must).analyze(jig) + + val aliasBeforeStatement = Array(jig.statements.size) { i -> + resolveAccessPathBase(daa.statesBeforeStmt[i], localVariableReachability, i) + } + + val aliasAfterStatement = Array(jig.statements.size) { i -> + resolveAccessPathBase(daa.statesAfterStmt[i], localVariableReachability, i) + } + + return compressMustAliasInfo(aliasBeforeStatement, aliasAfterStatement) + } + private fun compressAliasInfo( aliasBeforeStatement: Array>>, aliasAfterStatement: Array>> @@ -108,6 +123,18 @@ class JIRIntraProcAliasAnalysis( return JIRLocalAliasAnalysis.MethodAliasInfo(compressedBefore, compressedAfter) } + private fun compressMustAliasInfo( + aliasBeforeStatement: Array>>, + aliasAfterStatement: Array>> + ): JIRLocalAliasAnalysis.MethodMustAliasInfo { + val compressedBefore = arrayOfNulls>>(aliasBeforeStatement.size) + val compressedAfter = arrayOfNulls>>(aliasAfterStatement.size) + + compress(aliasBeforeStatement, compressedBefore, reference = null, referenceCompressed = null) + compress(aliasAfterStatement, compressedAfter, aliasBeforeStatement, compressedBefore) + return JIRLocalAliasAnalysis.MethodMustAliasInfo(compressedBefore, compressedAfter) + } + private fun compress( statementInfo: Array>>, compressed: Array>?>, @@ -139,6 +166,37 @@ class JIRIntraProcAliasAnalysis( } } + private fun compress( + statementInfo: Array>>, + compressed: Array>?>, + reference: Array>>?, + referenceCompressed: Array>?>? + ) { + for (i in statementInfo.indices) { + val current = statementInfo[i] + if (current.isEmpty()) continue + + if (i > 0 && statementInfo[i - 1] == current) { + compressed[i] = compressed[i - 1] + continue + } + + if (reference != null) { + if (reference[i] == current) { + compressed[i] = referenceCompressed!![i] + } + + if (i > 0 && reference[i - 1] == current) { + compressed[i] = referenceCompressed!![i - 1] + continue + } + } + + val unwrapped = JIRLocalAliasAnalysis.unwrapAllInfo(current) + compressed[i] = unwrapped + } + } + private fun resolveLocalVar( daa: ConnectedAliases, reachableLocals: JIRLocalVariableReachability, @@ -166,6 +224,35 @@ class JIRIntraProcAliasAnalysis( return result } + private fun AccessPathBase.isMustRelevantBase() = + this is AccessPathBase.LocalVar || this is AccessPathBase.Argument || this is AccessPathBase.This + + private fun resolveAccessPathBase( + daa: ConnectedAliases, + reachableLocals: JIRLocalVariableReachability, + instIdx: Int + ): Object2ObjectOpenHashMap> { + val result = Object2ObjectOpenHashMap>() + daa.aliasGroups.forEach { (_, group) -> + val converted = group + .flatMap { it.convertToAliasInfo(daa.aliasGroups, depth = 0) } + .filter { it !is AliasApInfo || reachableLocals.isReachable(it.base, instIdx) } + .distinct() + + // size == 1 means only local was converted to AliasInfo; not really meaningful + if (converted.size <= 1) return@forEach + + val bases = converted.filterIsInstance() + .filter { it.base.isMustRelevantBase() && it.accessors.isEmpty() } + .map { it.base } + + bases.forEach { base -> + result[base] = converted + } + } + return result + } + private fun AAInfo.convertToAliasInfo( aliasGroups: Int2ObjectOpenHashMap>, depth: Int diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/FactUtils.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/FactUtils.kt new file mode 100644 index 000000000..bc17a063f --- /dev/null +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/FactUtils.kt @@ -0,0 +1,150 @@ +package org.opentaint.dataflow.jvm.ap.ifds.analysis + +import org.opentaint.dataflow.ap.ifds.AccessPathBase +import org.opentaint.dataflow.ap.ifds.Accessor +import org.opentaint.dataflow.ap.ifds.AnyAccessor +import org.opentaint.dataflow.ap.ifds.access.FinalFactAp +import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis +import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasApInfo +import org.opentaint.ir.api.jvm.cfg.JIRInst + +data class AliasedFact(val fact: FinalFactAp, val alias: AliasApInfo?) + +object FactUtils { + /** + * @return Pair of lists, where left one has everything relevant to `suffix`, and right one has everything else. + */ + fun splitBySuffix(suffix: List, fact: FinalFactAp): Pair, List> { + val head = suffix.first() + val tail = suffix.drop(1) + if (tail.isEmpty()) { + if (fact.startsWithAccessor(AnyAccessor)) { + val factAfterAny = fact.readAccessor(AnyAccessor) + ?: error("Impossible") + + val clearHeadAfterAny = factAfterAny.clearAccessor(head) + val remainingAfterAny = clearHeadAfterAny?.prependAccessor(AnyAccessor) + + val withHeadAfterAny = factAfterAny.readAccessor(head) + val suffixAfterAny = withHeadAfterAny?.prependAccessor(head)?.prependAccessor(AnyAccessor) + + val factWithoutAny = fact.clearAccessor(AnyAccessor) + val remainingWithoutAny = factWithoutAny?.clearAccessor(head) + val suffixWithoutAny = factWithoutAny?.readAccessor(head)?.prependAccessor(head) + + val suffixFact = listOfNotNull(suffixAfterAny, suffixWithoutAny) + val remainingFact = listOfNotNull(remainingAfterAny, remainingWithoutAny) + + return suffixFact to remainingFact + } + + if (!fact.startsWithAccessor(head)) { + return emptyList() to listOf(fact) + } + + val remainingFact = listOfNotNull(fact.clearAccessor(head)) + val suffixFact = fact.readAccessor(head)?.prependAccessor(head) + + return listOfNotNull(suffixFact) to remainingFact + } + + val child = fact.readAccessor(head) + ?: return emptyList() to listOf(fact) + + val headRemaining = listOfNotNull(fact.clearAccessor(head)) + val (tailSuffix, tailRemaining) = splitBySuffix(tail, child) + val suffixRelated = tailSuffix.map { it.prependAccessor(head) } + val remaining = headRemaining + tailRemaining.map { it.prependAccessor(head) } + + return suffixRelated to remaining + } + + private fun rewriteForBase(fact: FinalFactAp, alias: AliasApInfo, newBase: AccessPathBase): AliasedFact { + val newFact = alias.accessors.fold(fact.rebase(newBase) as FinalFactAp?) { f, accessor -> + f?.readAccessor(accessor.apAccessor()) + } + check(newFact != null) { "Aliased fact did not contain all alias accessors!" } + return AliasedFact(newFact, alias) + } + + fun rewriteForAlias(fact: FinalFactAp, alias: AliasApInfo?): FinalFactAp { + if (alias == null) return fact + val newFact = alias.accessors.foldRight(fact.rebase(alias.base)) { accessor, f -> + f.prependAccessor(accessor.apAccessor()) + } + return newFact + } + + private fun splitByMustAlias( + fact: FinalFactAp, + mustAlias: AliasApInfo, + ): Pair, List> { + if (fact.base != mustAlias.base) { + return emptyList() to listOf(fact) + } + val aliasAccessors = mustAlias.accessors.map { it.apAccessor() } + return splitBySuffix(aliasAccessors, fact) + } + + fun splitFactByBaseMustAlias( + aliasAnalysis: JIRLocalAliasAnalysis?, + statement: JIRInst, + relevantBase: AccessPathBase, + fact: FinalFactAp, + includeOriginal: Boolean + ): Pair, List> { + val aliases = aliasAnalysis?.getMustAliases(statement, relevantBase).orEmpty() + var irrelevantFacts = listOf(fact) + val aliasedFacts = mutableListOf() + // hack for uniform calls in fieldWrite + if (includeOriginal) aliasedFacts.add(AliasedFact(fact, null)) + + aliases.forEach { alias -> + val left = mutableListOf() + irrelevantFacts.forEach { fact -> + val (aliased, irrelevant) = splitByMustAlias(fact, alias) + left.addAll(irrelevant) + aliased.forEach { aliasedFact -> + val rebasedFact = rewriteForBase(aliasedFact, alias, relevantBase) + aliasedFacts.add(rebasedFact) + } + } + irrelevantFacts = left + } + + // no splits happened, nothing was aliased, no irrelevant expected + if (irrelevantFacts.isNotEmpty() && irrelevantFacts.first() === fact) { + irrelevantFacts = emptyList() + } + + return aliasedFacts to irrelevantFacts + } + + fun splitFactMultipleBases( + aliasAnalysis: JIRLocalAliasAnalysis?, + statement: JIRInst, + relevantBases: List, + fact: FinalFactAp, + includeOriginal: Boolean + ): Pair, List> { + var irrelevantFacts = listOf(fact) + val aliasedFacts = mutableListOf() + if (includeOriginal) aliasedFacts.add(AliasedFact(fact, null)) + + relevantBases.forEach { base -> + val newIrrelevant = mutableListOf() + irrelevantFacts.forEach { fact -> + val (aliased, irrelevant) = + splitFactByBaseMustAlias(aliasAnalysis, statement, base, fact, false) + aliasedFacts.addAll(aliased) + newIrrelevant.addAll(irrelevant) + } + irrelevantFacts = newIrrelevant + } + if (irrelevantFacts.size == 1 && irrelevantFacts.first() === fact) { + irrelevantFacts = emptyList() + } + + return aliasedFacts to irrelevantFacts + } +} 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 a69373cec..9e2981f0b 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 @@ -22,6 +22,15 @@ fun JIRLocalAliasAnalysis.forEachAliasAtStatement(statement: JIRInst, fact: Fina .forEach { alias -> applyAlias(fact, alias, body) } } +fun JIRLocalAliasAnalysis.getMustAliases( + statement: JIRInst, + relevantBase: AccessPathBase +): List { + return findMustAlias(relevantBase, statement).orEmpty() + .filterIsInstance() + .filterNot { alias -> alias.base is AccessPathBase.Constant } +} + fun JIRLocalAliasAnalysis.forEachAliasAfterStatement(statement: JIRInst, fact: FinalFactAp, body: (FinalFactAp) -> Unit) { val base = fact.base as? AccessPathBase.LocalVar ?: return val aliases = findAliasAfterStatement(base, statement) ?: return 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 667f816e6..bc4894ac4 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 @@ -26,6 +26,7 @@ import org.opentaint.dataflow.jvm.ap.ifds.JIRMarkAwareConditionRewriter import org.opentaint.dataflow.jvm.ap.ifds.JIRMethodCallFactMapper import org.opentaint.dataflow.jvm.ap.ifds.JIRMethodPositionBaseTypeResolver import org.opentaint.dataflow.jvm.ap.ifds.JIRSimpleFactAwareConditionEvaluator +import org.opentaint.dataflow.jvm.ap.ifds.MethodFlowFunctionUtils import org.opentaint.dataflow.jvm.ap.ifds.TaintConfigUtils.applyCleaner import org.opentaint.dataflow.jvm.ap.ifds.TaintConfigUtils.applyPassThrough import org.opentaint.dataflow.jvm.ap.ifds.TaintConfigUtils.applyRuleWithAssumptions @@ -243,17 +244,34 @@ class JIRMethodCallFlowFunction( addCallToStart: (factReader: FinalFactReader, callerFact: FinalFactAp, startFactBase: AccessPathBase, TraceInfo) -> Unit, addUnchecked: (MethodCallFlowFunction.CallFact) -> Unit, ) { - if (!JIRMethodCallFactMapper.factIsRelevantToMethodCall(returnValue, callExpr, factAp)) { + val relevantBases = callExpr.operands.mapNotNull { MethodFlowFunctionUtils.accessPathBase(it) } + + val (aliasedFacts, irrelevantFacts) = + FactUtils.splitFactMultipleBases(analysisContext.aliasAnalysis, statement, relevantBases, factAp, true) + + var fixedFactAp: FinalFactAp? = null + + aliasedFacts.forEach { (fact, _) -> + if (JIRMethodCallFactMapper.factIsRelevantToMethodCall(returnValue, callExpr, fact)) + fixedFactAp = fact + } + + if (fixedFactAp == null) { skipCall() return } + irrelevantFacts.forEach { fact -> + val reader = FinalFactReader(fact, apManager) + addCallToReturn(reader, fact, TraceInfo.Flow) + } + val conditionRewriter = JIRMarkAwareConditionRewriter( CallPositionToJIRValueResolver(callExpr, returnValue), analysisContext, statement ) - val factReader = FinalFactReader(factAp, apManager) + val factReader = FinalFactReader(fixedFactAp, apManager) val markAfterAnyFieldResolver = createMarkAfterFieldsResolver( analysisContext.methodEntryPoint, initialFacts @@ -288,7 +306,7 @@ class JIRMethodCallFlowFunction( callee = callExpr.callee, callExpr = callExpr, returnValue = null, - factAp = factAp, + factAp = fixedFactAp, checker = analysisContext.factTypeChecker, ) { callerFact, startFactBase -> applyCleanersOrCallToStart( 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 10d9d2b92..0d89fb06a 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 @@ -10,6 +10,8 @@ import org.opentaint.dataflow.ap.ifds.analysis.MethodCallSummaryHandler import org.opentaint.dataflow.ap.ifds.analysis.MethodCallSummaryHandler.SummaryEdge import org.opentaint.dataflow.ap.ifds.analysis.MethodSequentFlowFunction.Sequent import org.opentaint.dataflow.jvm.ap.ifds.JIRMethodCallFactMapper +import org.opentaint.dataflow.jvm.ap.ifds.MethodFlowFunctionUtils +import org.opentaint.ir.api.jvm.cfg.JIRImmediate import org.opentaint.ir.api.jvm.cfg.JIRInst class JIRMethodCallSummaryHandler( @@ -51,6 +53,23 @@ class JIRMethodCallSummaryHandler( } } + val relevantBases = statement.operands.filterIsInstance() + .mapNotNull { MethodFlowFunctionUtils.accessPathBase(it) } + + val (aliasedFacts, _) = + FactUtils.splitFactMultipleBases( + analysisContext.aliasAnalysis, + statement, + relevantBases, + summaryFactAp, + false + ) + + aliasedFacts.forEach { (fact, alias) -> + val restored = FactUtils.rewriteForAlias(fact, alias) + result += handleSummaryEdge(initialFactRefinement, restored) + } + handleSummaryEdge(initialFactRefinement, summaryFactAp) } diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodSequentFlowFunction.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodSequentFlowFunction.kt index 0e0fb7315..63298a4af 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodSequentFlowFunction.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/JIRMethodSequentFlowFunction.kt @@ -17,6 +17,7 @@ import org.opentaint.dataflow.ap.ifds.analysis.MethodSequentFlowFunction.TraceIn import org.opentaint.dataflow.ap.ifds.taint.TaintSinkTracker.VulnerabilityTriggerPosition import org.opentaint.dataflow.configuration.jvm.ConstantTrue import org.opentaint.dataflow.jvm.ap.ifds.CalleePositionToJIRValueResolver +import org.opentaint.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis import org.opentaint.dataflow.jvm.ap.ifds.JIRMarkAwareConditionRewriter import org.opentaint.dataflow.jvm.ap.ifds.MethodFlowFunctionUtils import org.opentaint.dataflow.jvm.ap.ifds.MethodFlowFunctionUtils.accessPathBase @@ -548,14 +549,24 @@ class JIRMethodSequentFlowFunction( val accessor = accessors.first() - if (!factAp.mayRemoveAfterWrite(instance, accessor)) { + var fixedFact: FinalFactAp? = null + + val (aliased, irrelevant) = + FactUtils.splitFactByBaseMustAlias(analysisContext.aliasAnalysis, currentInst, instance, factAp, true) + + aliased.forEach { (fact, _) -> + if (fact.mayRemoveAfterWrite(instance, accessor)) + fixedFact = fact + } + + if (fixedFact == null) { // Fact is irrelevant to current writing unchanged(factAp) return } - if (factAp.isAbstract() && accessor !in factAp.exclusions) { - val nonAbstractAp = factAp.removeAbstraction() + if (fixedFact.isAbstract() && accessor !in fixedFact.exclusions) { + val nonAbstractAp = fixedFact.removeAbstraction() if (nonAbstractAp != null) { fieldWrite( instance, accessors, assignFrom, nonAbstractAp, @@ -563,15 +574,26 @@ class JIRMethodSequentFlowFunction( ) } - propagateAbstractFactWithFieldExcluded(factAp, accessor, propagateFactWithAccessorExclude) + propagateAbstractFactWithFieldExcluded(fixedFact, accessor, propagateFactWithAccessorExclude) return } - check(factAp.startsWithAccessor(accessor)) + irrelevant.forEach { fact -> propagateFact(fact) } + aliased.forEach { (fact, alias) -> + check(fact.startsWithAccessor(accessor)) - val newAp = factAp.clearField(accessor) ?: return - propagateFact(newAp) + val hasElementAccessor = alias?.accessors.orEmpty().any { it is JIRLocalAliasAnalysis.AliasAccessor.Array } + val newAp = + // todo hack: keep fact on the array elements + if (hasElementAccessor) fact + else fact.clearField(accessor) + + newAp?.let { + val restoredFact = FactUtils.rewriteForAlias(it, alias) + propagateFact(restoredFact) + } + } } private fun propagateAbstractFactWithFieldExcluded( From b158f5319f5de616d4e6a4f711a29584062f3e4c Mon Sep 17 00:00:00 2001 From: Daniil Logunov <43185213+DanielELog@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:04:30 +0300 Subject: [PATCH 05/12] Add must alias to unresolved call propagation --- .../analysis/JIRMethodCallFlowFunction.kt | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) 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 bc4894ac4..1f3abd613 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 @@ -629,9 +629,30 @@ class JIRMethodCallFlowFunction( addCallToReturn: (FinalFactReader, FinalFactAp, TraceInfo?) -> Unit, addSideEffectRequirement: (FinalFactReader) -> Unit, ) { - val factReader = FinalFactReader(factAp, apManager) + val relevantBases = callExpr.operands.mapNotNull { MethodFlowFunctionUtils.accessPathBase(it) } + + val (aliasedFacts, irrelevantFacts) = + FactUtils.splitFactMultipleBases(analysisContext.aliasAnalysis, statement, relevantBases, factAp, true) + + var fixedFactAp: FinalFactAp? = null - unresolvedCallDefaultFactPropagation(factReader, factAp, addCallToReturn) + aliasedFacts.forEach { (fact, _) -> + if (JIRMethodCallFactMapper.factIsRelevantToMethodCall(returnValue, callExpr, fact)) + fixedFactAp = fact + } + + if (fixedFactAp == null) { + val initialFactReader = FinalFactReader(factAp, apManager) + unresolvedCallDefaultFactPropagation(initialFactReader, factAp, addCallToReturn) + return + } + + irrelevantFacts.forEach { fact -> + val reader = FinalFactReader(fact, apManager) + addCallToReturn(reader, fact, TraceInfo.Flow) + } + + val factReader = FinalFactReader(fixedFactAp, apManager) val method = callExpr.callee val conditionRewriter = JIRMarkAwareConditionRewriter( @@ -643,7 +664,7 @@ class JIRMethodCallFlowFunction( callee = method, callExpr = callExpr, returnValue = null, - factAp = factAp, + factAp = fixedFactAp, checker = analysisContext.factTypeChecker ) { callerFact, startFactBase -> val passFactReader = FinalFactReader(callerFact.rebase(startFactBase), apManager) @@ -687,7 +708,7 @@ class JIRMethodCallFlowFunction( val trace = TraceInfo.Rule(evp.rule, evp.action) - mappedFact.forEachFactWithAliases(factAp) { + mappedFact.forEachFactWithAliases(fixedFactAp) { addCallToReturn(passFactReader, it, trace) } } From f1e5c13b185321c4bf602bb8d33df0578709ebb1 Mon Sep 17 00:00:00 2001 From: Daniil Logunov <43185213+DanielELog@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:21:50 +0300 Subject: [PATCH 06/12] Fix fact splitting --- .../jvm/ap/ifds/analysis/FactUtils.kt | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/FactUtils.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/FactUtils.kt index bc17a063f..26207fbe8 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/FactUtils.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/FactUtils.kt @@ -12,11 +12,13 @@ data class AliasedFact(val fact: FinalFactAp, val alias: AliasApInfo?) object FactUtils { /** - * @return Pair of lists, where left one has everything relevant to `suffix`, and right one has everything else. + * @return Pair of lists, where left one has everything relevant to `prefix`, and right one has everything else. */ - fun splitBySuffix(suffix: List, fact: FinalFactAp): Pair, List> { - val head = suffix.first() - val tail = suffix.drop(1) + fun splitByPrefix(prefix: List, fact: FinalFactAp): Pair, List> { + if (prefix.isEmpty()) return listOf(fact) to emptyList() + + val head = prefix.first() + val tail = prefix.drop(1) if (tail.isEmpty()) { if (fact.startsWithAccessor(AnyAccessor)) { val factAfterAny = fact.readAccessor(AnyAccessor) @@ -26,16 +28,16 @@ object FactUtils { val remainingAfterAny = clearHeadAfterAny?.prependAccessor(AnyAccessor) val withHeadAfterAny = factAfterAny.readAccessor(head) - val suffixAfterAny = withHeadAfterAny?.prependAccessor(head)?.prependAccessor(AnyAccessor) + val prefixAfterAny = withHeadAfterAny?.prependAccessor(head)?.prependAccessor(AnyAccessor) val factWithoutAny = fact.clearAccessor(AnyAccessor) val remainingWithoutAny = factWithoutAny?.clearAccessor(head) - val suffixWithoutAny = factWithoutAny?.readAccessor(head)?.prependAccessor(head) + val prefixWithoutAny = factWithoutAny?.readAccessor(head)?.prependAccessor(head) - val suffixFact = listOfNotNull(suffixAfterAny, suffixWithoutAny) + val prefixFact = listOfNotNull(prefixAfterAny, prefixWithoutAny) val remainingFact = listOfNotNull(remainingAfterAny, remainingWithoutAny) - return suffixFact to remainingFact + return prefixFact to remainingFact } if (!fact.startsWithAccessor(head)) { @@ -43,20 +45,20 @@ object FactUtils { } val remainingFact = listOfNotNull(fact.clearAccessor(head)) - val suffixFact = fact.readAccessor(head)?.prependAccessor(head) + val prefixFact = fact.readAccessor(head)?.prependAccessor(head) - return listOfNotNull(suffixFact) to remainingFact + return listOfNotNull(prefixFact) to remainingFact } val child = fact.readAccessor(head) ?: return emptyList() to listOf(fact) val headRemaining = listOfNotNull(fact.clearAccessor(head)) - val (tailSuffix, tailRemaining) = splitBySuffix(tail, child) - val suffixRelated = tailSuffix.map { it.prependAccessor(head) } + val (tailprefix, tailRemaining) = splitByPrefix(tail, child) + val prefixRelated = tailprefix.map { it.prependAccessor(head) } val remaining = headRemaining + tailRemaining.map { it.prependAccessor(head) } - return suffixRelated to remaining + return prefixRelated to remaining } private fun rewriteForBase(fact: FinalFactAp, alias: AliasApInfo, newBase: AccessPathBase): AliasedFact { @@ -83,7 +85,7 @@ object FactUtils { return emptyList() to listOf(fact) } val aliasAccessors = mustAlias.accessors.map { it.apAccessor() } - return splitBySuffix(aliasAccessors, fact) + return splitByPrefix(aliasAccessors, fact) } fun splitFactByBaseMustAlias( From 09c1e4126173f627c8141c1e00acfc7527609b17 Mon Sep 17 00:00:00 2001 From: Daniil Logunov <43185213+DanielELog@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:54:14 +0300 Subject: [PATCH 07/12] Fix must alias fact revival --- .../jvm/ap/ifds/analysis/FactUtils.kt | 2 +- .../jvm/ap/ifds/analysis/JIRAliasUtil.kt | 4 +++ .../analysis/JIRMethodCallFlowFunction.kt | 31 +++++-------------- .../analysis/JIRMethodCallSummaryHandler.kt | 17 ++-------- 4 files changed, 14 insertions(+), 40 deletions(-) diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/FactUtils.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/FactUtils.kt index 26207fbe8..0abb4c96d 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/FactUtils.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/analysis/FactUtils.kt @@ -98,7 +98,7 @@ object FactUtils { val aliases = aliasAnalysis?.getMustAliases(statement, relevantBase).orEmpty() var irrelevantFacts = listOf(fact) val aliasedFacts = mutableListOf() - // hack for uniform calls in fieldWrite + // hack for uniform calls if (includeOriginal) aliasedFacts.add(AliasedFact(fact, null)) aliases.forEach { alias -> 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 9e2981f0b..ed488f712 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 @@ -31,6 +31,10 @@ fun JIRLocalAliasAnalysis.getMustAliases( .filterNot { alias -> alias.base is AccessPathBase.Constant } } +fun JIRLocalAliasAnalysis.forEachMustAlias(statement: JIRInst, fact: FinalFactAp, body: (FinalFactAp) -> Unit) { + getMustAliases(statement, fact.base).forEach { alias -> applyAlias(fact, alias, body) } +} + fun JIRLocalAliasAnalysis.forEachAliasAfterStatement(statement: JIRInst, fact: FinalFactAp, body: (FinalFactAp) -> Unit) { val base = fact.base as? AccessPathBase.LocalVar ?: return val aliases = findAliasAfterStatement(base, statement) ?: return 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 1f3abd613..bf462a65e 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 @@ -629,30 +629,13 @@ class JIRMethodCallFlowFunction( addCallToReturn: (FinalFactReader, FinalFactAp, TraceInfo?) -> Unit, addSideEffectRequirement: (FinalFactReader) -> Unit, ) { - val relevantBases = callExpr.operands.mapNotNull { MethodFlowFunctionUtils.accessPathBase(it) } - - val (aliasedFacts, irrelevantFacts) = - FactUtils.splitFactMultipleBases(analysisContext.aliasAnalysis, statement, relevantBases, factAp, true) - - var fixedFactAp: FinalFactAp? = null - - aliasedFacts.forEach { (fact, _) -> - if (JIRMethodCallFactMapper.factIsRelevantToMethodCall(returnValue, callExpr, fact)) - fixedFactAp = fact - } - - if (fixedFactAp == null) { - val initialFactReader = FinalFactReader(factAp, apManager) - unresolvedCallDefaultFactPropagation(initialFactReader, factAp, addCallToReturn) - return + analysisContext.aliasAnalysis?.forEachMustAlias(statement, factAp) { fact -> + val aliasReader = FinalFactReader(fact, apManager) + unresolvedCallDefaultFactPropagation(aliasReader, fact, addCallToReturn) } - irrelevantFacts.forEach { fact -> - val reader = FinalFactReader(fact, apManager) - addCallToReturn(reader, fact, TraceInfo.Flow) - } - - val factReader = FinalFactReader(fixedFactAp, apManager) + val factReader = FinalFactReader(factAp, apManager) + unresolvedCallDefaultFactPropagation(factReader, factAp, addCallToReturn) val method = callExpr.callee val conditionRewriter = JIRMarkAwareConditionRewriter( @@ -664,7 +647,7 @@ class JIRMethodCallFlowFunction( callee = method, callExpr = callExpr, returnValue = null, - factAp = fixedFactAp, + factAp = factAp, checker = analysisContext.factTypeChecker ) { callerFact, startFactBase -> val passFactReader = FinalFactReader(callerFact.rebase(startFactBase), apManager) @@ -708,7 +691,7 @@ class JIRMethodCallFlowFunction( val trace = TraceInfo.Rule(evp.rule, evp.action) - mappedFact.forEachFactWithAliases(fixedFactAp) { + mappedFact.forEachFactWithAliases(factAp) { addCallToReturn(passFactReader, it, trace) } } 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 0d89fb06a..56916dbc4 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 @@ -53,21 +53,8 @@ class JIRMethodCallSummaryHandler( } } - val relevantBases = statement.operands.filterIsInstance() - .mapNotNull { MethodFlowFunctionUtils.accessPathBase(it) } - - val (aliasedFacts, _) = - FactUtils.splitFactMultipleBases( - analysisContext.aliasAnalysis, - statement, - relevantBases, - summaryFactAp, - false - ) - - aliasedFacts.forEach { (fact, alias) -> - val restored = FactUtils.rewriteForAlias(fact, alias) - result += handleSummaryEdge(initialFactRefinement, restored) + analysisContext.aliasAnalysis?.forEachMustAlias(statement, summaryFactAp) { fact -> + result += handleSummaryEdge(initialFactRefinement, fact) } handleSummaryEdge(initialFactRefinement, summaryFactAp) From ac4b1d69b52815dd903847eba6f8c37f54353a94 Mon Sep 17 00:00:00 2001 From: Daniil Logunov <43185213+DanielELog@users.noreply.github.com> Date: Fri, 3 Apr 2026 19:39:27 +0300 Subject: [PATCH 08/12] Add simple semgrep test for must alias --- .../main/java/issues/i98/CleanableData.java | 15 +++++++++++++ .../src/main/java/issues/i98/User.java | 16 ++++++++++++++ .../samples/src/main/java/issues/issue98.java | 22 +++++++++++++++++++ .../src/main/resources/issues/issue98.yaml | 17 ++++++++++++++ .../org/opentaint/semgrep/IssuesTest.kt | 4 ++++ 5 files changed, 74 insertions(+) create mode 100644 core/opentaint-java-querylang/samples/src/main/java/issues/i98/CleanableData.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/issues/i98/User.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/issues/issue98.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/CleanableData.java b/core/opentaint-java-querylang/samples/src/main/java/issues/i98/CleanableData.java new file mode 100644 index 000000000..818e0ffca --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/issues/i98/CleanableData.java @@ -0,0 +1,15 @@ +package issues.i98; + +public class CleanableData { + public String info; + + public CleanableData() { + this.info = "so unsafe"; + } + + public void cleanData() { } + + public String getInfo() { + return info; + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/issues/i98/User.java b/core/opentaint-java-querylang/samples/src/main/java/issues/i98/User.java new file mode 100644 index 000000000..c3931d7bb --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/issues/i98/User.java @@ -0,0 +1,16 @@ +package issues.i98; + +public class User { + private CleanableData data; + + public String peculiarMethod() { + this.data = new CleanableData(); + this.data.cleanData(); + return this.data.getInfo(); + } + + public String badMethod() { + this.data = new CleanableData(); + return this.data.getInfo(); + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/issues/issue98.java b/core/opentaint-java-querylang/samples/src/main/java/issues/issue98.java new file mode 100644 index 000000000..166ebbf48 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/issues/issue98.java @@ -0,0 +1,22 @@ +package issues; + +import base.RuleSample; +import base.RuleSet; +import issues.i98.User; + +@RuleSet("issues/issue98.yaml") +public abstract class issue98 implements RuleSample { + static class NegativeTaint extends issue98 { + @Override + public void entrypoint() { + new User().peculiarMethod(); + } + } + + static class PositiveTaint extends issue98 { + @Override + public void entrypoint() { + new User().badMethod(); + } + } +} 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..79333c2ef --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml @@ -0,0 +1,17 @@ +rules: + - id: i98 + languages: + - java + severity: ERROR + message: cleanedMethod + patterns: + - pattern: | + $A = new CleanableData(); + ... + return $A.getInfo(); + - pattern-not-inside: | + $A = new CleanableData(); + ... + $A.cleanData(); + ... + return $A.getInfo(); 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 185fe851c..0eb5f36a7 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 @@ -16,6 +16,7 @@ import issues.issue94 import issues.issue95 import issues.issue96 import issues.issue97 +import issues.issue98 import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.TestInstance @@ -105,6 +106,9 @@ class IssuesTest : SampleBasedTest() { @Test fun `issue 97`() = runTest() + @Test + fun `issue 98`() = runTest(expectStateVar = true) + @AfterAll fun close() { closeRunner() From fa027c72e165a528537588e43fc0b144306d59fd Mon Sep 17 00:00:00 2001 From: Daniil Logunov <43185213+DanielELog@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:43:42 +0300 Subject: [PATCH 09/12] Add example tests for must alias --- .../main/java/example/MustAliasExample.java | 64 +++++++++++++++++++ .../i98 => example/util}/CleanableData.java | 6 +- .../src/main/java/issues/i98/User.java | 16 ----- .../samples/src/main/java/issues/issue98.java | 22 ------- .../MustAliasExample.yaml} | 6 +- .../org/opentaint/semgrep/ExampleTest.kt | 3 + .../org/opentaint/semgrep/IssuesTest.kt | 4 -- 7 files changed, 72 insertions(+), 49 deletions(-) create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/MustAliasExample.java rename core/opentaint-java-querylang/samples/src/main/java/{issues/i98 => example/util}/CleanableData.java (67%) delete mode 100644 core/opentaint-java-querylang/samples/src/main/java/issues/i98/User.java delete mode 100644 core/opentaint-java-querylang/samples/src/main/java/issues/issue98.java rename core/opentaint-java-querylang/samples/src/main/resources/{issues/issue98.yaml => example/MustAliasExample.yaml} (79%) diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/MustAliasExample.java b/core/opentaint-java-querylang/samples/src/main/java/example/MustAliasExample.java new file mode 100644 index 000000000..a03537c4a --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/MustAliasExample.java @@ -0,0 +1,64 @@ +package example; + +import base.RuleSample; +import base.RuleSet; +import example.util.CleanableData; + +@RuleSet("example/MustAliasExample.yaml") +public abstract class MustAliasExample implements RuleSample { + protected CleanableData data; + + protected void cleanData(CleanableData data) { + data.cleanData(); + } + + protected void doNothing(CleanableData data) { } + + static class NegativeSimpleFlowTaint extends MustAliasExample { + @Override + public void entrypoint() { + this.data = new CleanableData(); + this.data.cleanData(); + this.data.sendInfo(); + } + } + + static class PositiveSimpleFlowTaint extends MustAliasExample { + @Override + public void entrypoint() { + this.data = new CleanableData(); + this.data.sendInfo(); + } + } + + static class NegativeMethodFlowTaint extends MustAliasExample { + @Override + public void entrypoint() { + CleanableData obj = new CleanableData(); + CleanableData obj2 = obj; + cleanData(obj2); + obj.sendInfo(); + } + } + + static class PositiveMethodFlowTaint extends MustAliasExample { + @Override + public void entrypoint() { + CleanableData obj = new CleanableData(); + CleanableData obj2 = obj; + if (obj.info.length() > 5) { + cleanData(obj2); + } + obj.sendInfo(); + } + } + + static class PositiveMethodFlow2Taint extends MustAliasExample { + @Override + public void entrypoint() { + CleanableData obj = new CleanableData(); + doNothing(obj); + obj.sendInfo(); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/issues/i98/CleanableData.java b/core/opentaint-java-querylang/samples/src/main/java/example/util/CleanableData.java similarity index 67% rename from core/opentaint-java-querylang/samples/src/main/java/issues/i98/CleanableData.java rename to core/opentaint-java-querylang/samples/src/main/java/example/util/CleanableData.java index 818e0ffca..ac55088a8 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/issues/i98/CleanableData.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/util/CleanableData.java @@ -1,4 +1,4 @@ -package issues.i98; +package example.util; public class CleanableData { public String info; @@ -9,7 +9,5 @@ public CleanableData() { public void cleanData() { } - public String getInfo() { - return info; - } + public void sendInfo() { } } diff --git a/core/opentaint-java-querylang/samples/src/main/java/issues/i98/User.java b/core/opentaint-java-querylang/samples/src/main/java/issues/i98/User.java deleted file mode 100644 index c3931d7bb..000000000 --- a/core/opentaint-java-querylang/samples/src/main/java/issues/i98/User.java +++ /dev/null @@ -1,16 +0,0 @@ -package issues.i98; - -public class User { - private CleanableData data; - - public String peculiarMethod() { - this.data = new CleanableData(); - this.data.cleanData(); - return this.data.getInfo(); - } - - public String badMethod() { - this.data = new CleanableData(); - return this.data.getInfo(); - } -} diff --git a/core/opentaint-java-querylang/samples/src/main/java/issues/issue98.java b/core/opentaint-java-querylang/samples/src/main/java/issues/issue98.java deleted file mode 100644 index 166ebbf48..000000000 --- a/core/opentaint-java-querylang/samples/src/main/java/issues/issue98.java +++ /dev/null @@ -1,22 +0,0 @@ -package issues; - -import base.RuleSample; -import base.RuleSet; -import issues.i98.User; - -@RuleSet("issues/issue98.yaml") -public abstract class issue98 implements RuleSample { - static class NegativeTaint extends issue98 { - @Override - public void entrypoint() { - new User().peculiarMethod(); - } - } - - static class PositiveTaint extends issue98 { - @Override - public void entrypoint() { - new User().badMethod(); - } - } -} diff --git a/core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/MustAliasExample.yaml similarity index 79% rename from core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml rename to core/opentaint-java-querylang/samples/src/main/resources/example/MustAliasExample.yaml index 79333c2ef..ae9971a43 100644 --- a/core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/MustAliasExample.yaml @@ -1,5 +1,5 @@ rules: - - id: i98 + - id: example-Rule languages: - java severity: ERROR @@ -8,10 +8,10 @@ rules: - pattern: | $A = new CleanableData(); ... - return $A.getInfo(); + $A.sendInfo(); - pattern-not-inside: | $A = new CleanableData(); ... $A.cleanData(); ... - return $A.getInfo(); + $A.sendInfo(); diff --git a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/ExampleTest.kt b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/ExampleTest.kt index 9a874949f..b15c05f8a 100644 --- a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/ExampleTest.kt +++ b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/ExampleTest.kt @@ -195,6 +195,9 @@ class ExampleTest : SampleBasedTest() { @Test fun `test array example`() = runTest() + @Test + fun `test must alias examples`() = runTest() + @Test fun `test join with taint and matching left`() = runTest() 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 0eb5f36a7..185fe851c 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 @@ -16,7 +16,6 @@ import issues.issue94 import issues.issue95 import issues.issue96 import issues.issue97 -import issues.issue98 import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.TestInstance @@ -106,9 +105,6 @@ class IssuesTest : SampleBasedTest() { @Test fun `issue 97`() = runTest() - @Test - fun `issue 98`() = runTest(expectStateVar = true) - @AfterAll fun close() { closeRunner() From 9267fd50678e4f7fbac610a69a42f15e17baf776 Mon Sep 17 00:00:00 2001 From: Daniil Logunov <43185213+DanielELog@users.noreply.github.com> Date: Thu, 9 Apr 2026 18:34:44 +0300 Subject: [PATCH 10/12] Fix alias application on call instance --- .../jvm/ap/ifds/alias/DSUAliasAnalysis.kt | 11 +++-------- .../jvm/ap/ifds/alias/JIRDSUAABuilder.kt | 11 +---------- .../src/main/java/example/MustAliasExample.java | 16 ++++++++++++++++ .../kotlin/org/opentaint/semgrep/ExampleTest.kt | 7 ++++++- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt index b7f581e3f..bf8b7d32e 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt @@ -219,15 +219,13 @@ class DSUAliasAnalysis( } else state if (stmt.cantMutateAliasedHeap()) return resultState - val instanceIndex = stmt.instance?.aliasInfo()?.index() - val argAliases = IntOpenHashSet() - stmt.getUsedValues().forEach { arg -> + stmt.args.forEach { arg -> val info = arg.aliasInfo() ?: return@forEach val infoIndex = aliasManager.getOrAdd(info) resultState.forEachAliasInSet(infoIndex) { argAliases.add(it) } } - return resultState.invalidateOuterHeapAliases(argAliases, instanceIndex) + return resultState.invalidateOuterHeapAliases(argAliases) } private fun evalCall( @@ -257,12 +255,9 @@ class DSUAliasAnalysis( return State.merge(aliasManager, dsuMergeStrategy, statesAfterCall, mergeType) } - private fun State.invalidateOuterHeapAliases(startInvalidAliases: IntOpenHashSet, instance: Int?): State { + private fun State.invalidateOuterHeapAliases(startInvalidAliases: IntOpenHashSet): State { val invalidAliases = collectTransitiveInvalidAliases(startInvalidAliases) - // keeping `this` aliases - instance?.let { invalidAliases.remove(it) } - val invalidHeapAliases = IntOpenHashSet() invalidAliases.forEach { val element = aliasManager.getElementUncheck(it) diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt index 41bb94f3f..e5c344ede 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt @@ -90,16 +90,7 @@ sealed interface Stmt : Comparable { sealed interface NoCall: Stmt - data class Call(val method: JIRMethod, val lValue: RefValue.Local?, val instance: Value?, val args: List, override val originalIdx: Int) : Stmt { - private val usedValuesValue = lazy { usedValues() } - - private fun usedValues(): List { - if (instance == null) return args - return args + instance - } - - fun getUsedValues() = usedValuesValue.value - } + data class Call(val method: JIRMethod, val lValue: RefValue.Local?, val instance: Value?, val args: List, override val originalIdx: Int) : Stmt data class Copy(val lValue: RefValue.Local, val rValue: RefValue, override val originalIdx: Int): NoCall data class Assign(val lValue: RefValue.Local, val expr: Expr, override val originalIdx: Int) : NoCall diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/MustAliasExample.java b/core/opentaint-java-querylang/samples/src/main/java/example/MustAliasExample.java index a03537c4a..0caf4f9c5 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/MustAliasExample.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/MustAliasExample.java @@ -3,6 +3,7 @@ import base.RuleSample; import base.RuleSet; import example.util.CleanableData; +import java.util.Map; @RuleSet("example/MustAliasExample.yaml") public abstract class MustAliasExample implements RuleSample { @@ -61,4 +62,19 @@ public void entrypoint() { obj.sendInfo(); } } + + protected Map dataMap; + + protected void putData(CleanableData data) { + dataMap.put(0, data); + } + + static class PositiveMapFlowTaint extends MustAliasExample { + @Override + public void entrypoint() { + CleanableData obj = new CleanableData(); + putData(obj); + dataMap.get(0).sendInfo(); + } + } } diff --git a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/ExampleTest.kt b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/ExampleTest.kt index b15c05f8a..fbe0bee31 100644 --- a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/ExampleTest.kt +++ b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/ExampleTest.kt @@ -6,6 +6,7 @@ import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import org.opentaint.config.ConfigLoader import org.opentaint.dataflow.configuration.jvm.serialized.PositionBase import org.opentaint.dataflow.configuration.jvm.serialized.SerializedRule import org.opentaint.dataflow.configuration.jvm.serialized.SerializedSimpleNameMatcher.Simple @@ -13,6 +14,7 @@ import org.opentaint.dataflow.configuration.jvm.serialized.SerializedTaintPassAc import org.opentaint.semgrep.pattern.conversion.taint.anyFunction import org.opentaint.semgrep.pattern.conversion.taint.base import org.opentaint.semgrep.util.SampleBasedTest +import kotlin.collections.orEmpty import kotlin.test.Test @TestInstance(PER_CLASS) @@ -196,7 +198,10 @@ class ExampleTest : SampleBasedTest() { fun `test array example`() = runTest() @Test - fun `test must alias examples`() = runTest() + fun `test must alias examples`() = runTest { cfg -> + val config = ConfigLoader.getConfig()?.passThrough.orEmpty() + cfg.copy(passThrough = cfg.passThrough.orEmpty() + config) + } @Test fun `test join with taint and matching left`() = runTest() From 1481d870ab554931f623c1ba6da36c0bec810527 Mon Sep 17 00:00:00 2001 From: Daniil Logunov <43185213+DanielELog@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:58:40 +0300 Subject: [PATCH 11/12] Fix original base filtering from alias group --- .../opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt index 4239ff8c5..abfcd6561 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt @@ -42,7 +42,7 @@ class JIRLocalAliasAnalysis( instIdx: Int, base: AccessPathBase.LocalVar ): List? = alias[instIdx]?.getOrDefault(base.idx, null)?.filter { - it !is AliasApInfo || it.accessors.isNotEmpty() || it.base != base + it !is AccessPathBase || it != base }?.map { it.wrapAliasInfo() } private fun getAccessPathBaseAliases( @@ -50,7 +50,7 @@ class JIRLocalAliasAnalysis( instIdx: Int, base: AccessPathBase ): List? = alias[instIdx]?.getOrDefault(base, null)?.filter { - it !is AliasApInfo || it.accessors.isNotEmpty() || it.base != base + it !is AccessPathBase || it != base }?.map { it.wrapAliasInfo() } fun findMustAlias(base: AccessPathBase, statement: CommonInst): List? { From fa0e776caf28bf11dbde7bf48c079f9fdb10a904 Mon Sep 17 00:00:00 2001 From: Valentyn Sobol <8640896+Saloed@users.noreply.github.com> Date: Wed, 22 Apr 2026 20:27:50 +0300 Subject: [PATCH 12/12] Fix after rebase --- .../jvm/ap/ifds/JIRLocalAliasAnalysis.kt | 7 +- .../ifds/alias/JIRIntraProcAliasAnalysis.kt | 31 +++++-- .../opentaint/dataflow/jvm/BasicTestUtils.kt | 82 ++++++++++++++++++- 3 files changed, 110 insertions(+), 10 deletions(-) diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt index abfcd6561..efa9077ac 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt @@ -33,8 +33,8 @@ class JIRLocalAliasAnalysis( ) class MethodMustAliasInfo( - val aliasBeforeStatement: Array>?>, - val aliasAfterStatement: Array>?>, + val aliasBeforeStatement: Array>?>?, + val aliasAfterStatement: Array>?>?, ) private fun getLocalVarAliases( @@ -54,8 +54,9 @@ class JIRLocalAliasAnalysis( }?.map { it.wrapAliasInfo() } fun findMustAlias(base: AccessPathBase, statement: CommonInst): List? { + val aliasBefore = mustAliasInfo.aliasBeforeStatement ?: return null val idx = languageManager.getInstIndex(statement) - return getAccessPathBaseAliases(mustAliasInfo.aliasBeforeStatement, idx, base) + return getAccessPathBaseAliases(aliasBefore, idx, base) } fun findAlias(base: AccessPathBase.LocalVar, statement: CommonInst): List? { diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt index 6ebab9f56..92bd1a585 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/main/kotlin/org/opentaint/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt @@ -65,10 +65,10 @@ class JIRIntraProcAliasAnalysis( ): JIRLocalAliasAnalysis.MethodAliasInfo = withAnalysisCancellation( timeLimit = params.aliasAnalysisTimeLimit, - body = { compute(it, localVariableReachability) }, + body = { computeMay(it, localVariableReachability) }, onAnalysisCancelled = { logger.error { - "Alias analysis for ${entryPoint.location.method} exceed ${params.aliasAnalysisTimeLimit}" + "May alias analysis for ${entryPoint.location.method} exceed ${params.aliasAnalysisTimeLimit}" } JIRLocalAliasAnalysis.MethodAliasInfo( @@ -78,7 +78,25 @@ class JIRIntraProcAliasAnalysis( } ) - private fun compute( + fun computeMust( + localVariableReachability: JIRLocalVariableReachability + ): JIRLocalAliasAnalysis.MethodMustAliasInfo = + withAnalysisCancellation( + timeLimit = params.aliasAnalysisTimeLimit, + body = { computeMust(it, localVariableReachability) }, + onAnalysisCancelled = { + logger.error { + "Must alias analysis for ${entryPoint.location.method} exceed ${params.aliasAnalysisTimeLimit}" + } + + JIRLocalAliasAnalysis.MethodMustAliasInfo( + aliasBeforeStatement = null, + aliasAfterStatement = null + ) + } + ) + + private fun computeMay( cancellation: AnalysisCancellation, localVariableReachability: JIRLocalVariableReachability ): JIRLocalAliasAnalysis.MethodAliasInfo { @@ -96,9 +114,12 @@ class JIRIntraProcAliasAnalysis( return compressAliasInfo(aliasBeforeStatement, aliasAfterStatement) } - fun computeMust(localVariableReachability: JIRLocalVariableReachability): JIRLocalAliasAnalysis.MethodMustAliasInfo { + private fun computeMust( + cancellation: AnalysisCancellation, + localVariableReachability: JIRLocalVariableReachability + ): JIRLocalAliasAnalysis.MethodMustAliasInfo { val jig = getJIG(entryPoint) - val daa = DSUAliasAnalysis(CallResolver(), localVariableReachability, MergeType.Must).analyze(jig) + val daa = DSUAliasAnalysis(CallResolver(), localVariableReachability, MergeType.Must, cancellation).analyze(jig) val aliasBeforeStatement = Array(jig.statements.size) { i -> resolveAccessPathBase(daa.statesBeforeStmt[i], localVariableReachability, i) diff --git a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/BasicTestUtils.kt b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/BasicTestUtils.kt index df2c0e8f2..4515bbd04 100644 --- a/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/BasicTestUtils.kt +++ b/core/opentaint-dataflow-core/opentaint-jvm-dataflow/src/test/kotlin/org/opentaint/dataflow/jvm/BasicTestUtils.kt @@ -5,6 +5,17 @@ import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.TestInstance import org.opentaint.dataflow.ap.ifds.AccessPathBase +import org.opentaint.dataflow.ap.ifds.access.FactAp +import org.opentaint.dataflow.ap.ifds.access.InitialFactAp +import org.opentaint.dataflow.configuration.jvm.TaintCleaner +import org.opentaint.dataflow.configuration.jvm.TaintEntryPointSource +import org.opentaint.dataflow.configuration.jvm.TaintMethodEntrySink +import org.opentaint.dataflow.configuration.jvm.TaintMethodExitSink +import org.opentaint.dataflow.configuration.jvm.TaintMethodExitSource +import org.opentaint.dataflow.configuration.jvm.TaintMethodSink +import org.opentaint.dataflow.configuration.jvm.TaintMethodSource +import org.opentaint.dataflow.configuration.jvm.TaintPassThrough +import org.opentaint.dataflow.configuration.jvm.TaintStaticFieldSource import org.opentaint.dataflow.ifds.SingletonUnit import org.opentaint.dataflow.ifds.UnitType import org.opentaint.dataflow.ifds.UnknownUnit @@ -14,9 +25,13 @@ 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.JIRLocalVariableReachability import org.opentaint.dataflow.jvm.ap.ifds.analysis.JIRAnalysisManager +import org.opentaint.dataflow.jvm.ap.ifds.taint.TaintRulesProvider import org.opentaint.dataflow.jvm.ifds.JIRUnitResolver +import org.opentaint.ir.api.common.CommonMethod +import org.opentaint.ir.api.common.cfg.CommonInst import org.opentaint.ir.api.jvm.JIRClasspath import org.opentaint.ir.api.jvm.JIRDatabase +import org.opentaint.ir.api.jvm.JIRField import org.opentaint.ir.api.jvm.JIRMethod import org.opentaint.ir.api.jvm.RegisteredLocation import org.opentaint.ir.api.jvm.cfg.JIRCallInst @@ -31,7 +46,6 @@ import org.opentaint.ir.impl.features.usagesExt import org.opentaint.ir.impl.opentaintIrDb import org.opentaint.jvm.graph.JApplicationGraphImpl import java.nio.file.Path -import kotlin.collections.orEmpty import kotlin.io.path.Path @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -40,7 +54,71 @@ abstract class BasicTestUtils { protected lateinit var db: JIRDatabase protected lateinit var cp: JIRClasspath - protected val manager by lazy { JIRAnalysisManager(cp) } + private val noRules = object : TaintRulesProvider { + override fun entryPointRulesForMethod( + method: CommonMethod, + fact: FactAp?, + allRelevant: Boolean + ): Iterable = emptyList() + + override fun sourceRulesForMethod( + method: CommonMethod, + statement: CommonInst, + fact: FactAp?, + allRelevant: Boolean + ): Iterable = emptyList() + + override fun exitSourceRulesForMethod( + method: CommonMethod, + statement: CommonInst, + fact: FactAp?, + allRelevant: Boolean + ): Iterable = emptyList() + + override fun sinkRulesForMethod( + method: CommonMethod, + statement: CommonInst, + fact: FactAp?, + allRelevant: Boolean + ): Iterable = emptyList() + + override fun sinkRulesForMethodEntry( + method: CommonMethod, + fact: FactAp?, + allRelevant: Boolean + ): Iterable = emptyList() + + override fun sinkRulesForMethodExit( + method: CommonMethod, + statement: CommonInst, + fact: FactAp?, + initialFacts: Set?, + allRelevant: Boolean + ): Iterable = emptyList() + + override fun passTroughRulesForMethod( + method: CommonMethod, + statement: CommonInst, + fact: FactAp?, + allRelevant: Boolean + ): Iterable = emptyList() + + override fun cleanerRulesForMethod( + method: CommonMethod, + statement: CommonInst, + fact: FactAp?, + allRelevant: Boolean + ): Iterable = emptyList() + + override fun sourceRulesForStaticField( + field: JIRField, + statement: CommonInst, + fact: FactAp?, + allRelevant: Boolean + ): Iterable = emptyList() + } + + protected val manager by lazy { JIRAnalysisManager(cp, noRules) } @BeforeAll fun setup() {