From 8df49ed8d1cea9ff404bb79251804ef598785da5 Mon Sep 17 00:00:00 2001 From: seqradev Date: Thu, 12 Feb 2026 13:26:49 +0000 Subject: [PATCH] fix: Improve alias handling and inter-proc analysis robustness --- .../seqra/dataflow/ap/ifds/MethodAnalyzer.kt | 32 ++- .../dataflow/ap/ifds/TaintAnalysisManager.kt | 11 +- .../ap/ifds/TaintAnalysisUnitRunnerManager.kt | 6 +- .../seqra/dataflow/ap/ifds/access/FactAp.kt | 1 + .../access/automata/AccessGraphFinalFactAp.kt | 7 + .../ap/ifds/access/cactus/AccessCactus.kt | 4 + .../ap/ifds/access/tree/AccessTree.kt | 26 ++ .../ap/ifds/analysis/AnalysisManager.kt | 3 +- .../ifds/analysis/MethodCallSummaryHandler.kt | 23 +- .../ifds/trace/MethodForwardTraceResolver.kt | 6 +- .../dataflow/graph/CompactGraphSimulator.kt | 14 +- .../dataflow/jvm/ap/ifds/JIRCallResolver.kt | 26 +- .../jvm/ap/ifds/JIRLocalAliasAnalysis.kt | 12 +- .../jvm/ap/ifds/alias/DSUAliasAnalysis.kt | 270 +++++++++++++++--- .../jvm/ap/ifds/alias/InterProcCallNode.kt | 98 +++++++ .../jvm/ap/ifds/alias/JIRDSUAABuilder.kt | 63 ++-- .../jvm/ap/ifds/alias/JIRDSUAASimulator.kt | 2 + .../ifds/alias/JIRIntraProcAliasAnalysis.kt | 22 +- .../jvm/ap/ifds/analysis/JIRAliasUtil.kt | 14 +- .../ap/ifds/analysis/JIRAnalysisManager.kt | 9 +- .../analysis/JIRMethodCallFlowFunction.kt | 38 ++- .../ap/ifds/analysis/JIRMethodCallResolver.kt | 2 +- .../analysis/JIRMethodCallSummaryHandler.kt | 16 +- 23 files changed, 560 insertions(+), 145 deletions(-) create mode 100644 seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/InterProcCallNode.kt diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/MethodAnalyzer.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/MethodAnalyzer.kt index 1dbedd9..7d80e1a 100644 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/MethodAnalyzer.kt +++ b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/MethodAnalyzer.kt @@ -17,6 +17,7 @@ import org.seqra.dataflow.ap.ifds.access.InitialFactAp import org.seqra.dataflow.ap.ifds.analysis.MethodCallFlowFunction import org.seqra.dataflow.ap.ifds.analysis.MethodCallFlowFunction.ZeroCallFact import org.seqra.dataflow.ap.ifds.analysis.MethodCallSummaryHandler +import org.seqra.dataflow.ap.ifds.analysis.MethodCallSummaryHandler.SummaryEdge import org.seqra.dataflow.ap.ifds.analysis.MethodSequentFlowFunction.Sequent import org.seqra.dataflow.ap.ifds.analysis.MethodStartFlowFunction.StartFact import org.seqra.dataflow.ap.ifds.trace.MethodForwardTraceResolver @@ -173,7 +174,9 @@ class NormalMethodAnalyzer( private var pendingSideEffectRequirements = arrayListOf() private var pendingSideEffectSummaries = arrayListOf() - private val analysisContext = analysisManager.getMethodAnalysisContext(methodEntryPoint, runner.graph) + private val analysisContext = analysisManager.getMethodAnalysisContext( + methodEntryPoint, runner.graph, runner.methodCallResolver + ) private var analyzerEnqueued = false private var unprocessedEdges = arrayListOf() @@ -903,8 +906,8 @@ class NormalMethodAnalyzer( currentEdgeFactAp = sub.currentEdge.factAp, methodInitialFactBase = sub.methodInitialFactBase, methodSummaries = applicableSummaries, - handleSummaryEdge = { currentFactAp: FinalFactAp, summaryEffect: SummaryEdgeApplication, summaryFact: FinalFactAp -> - handler.handleFactToFact(sub.currentEdge.initialFactAp, currentFactAp, summaryEffect, summaryFact) + handleSummaryEdge = { currentFactAp: FinalFactAp, summaryEffect: SummaryEdgeApplication, summaryEdge: SummaryEdge -> + handler.handleFactToFact(sub.currentEdge.initialFactAp, currentFactAp, summaryEffect, summaryEdge) } ) } @@ -938,8 +941,8 @@ class NormalMethodAnalyzer( currentEdgeFactAp = sub.currentEdge.factAp, methodInitialFactBase = sub.methodInitialFactBase, methodSummaries = applicableSummaries, - handleSummaryEdge = { currentFactAp: FinalFactAp, summaryEffect: SummaryEdgeApplication, summaryFact: FinalFactAp -> - handler.handleNDFactToFact(sub.currentEdge.initialFacts, currentFactAp, summaryEffect, summaryFact) + handleSummaryEdge = { currentFactAp: FinalFactAp, summaryEffect: SummaryEdgeApplication, summaryEdge: SummaryEdge -> + handler.handleNDFactToFact(sub.currentEdge.initialFacts, currentFactAp, summaryEffect, summaryEdge) } ) } @@ -960,7 +963,7 @@ class NormalMethodAnalyzer( currentEdgeFactAp: FinalFactAp, methodInitialFactBase: AccessPathBase, methodSummaries: List, - handleSummaryEdge: (currentFactAp: FinalFactAp, summaryEffect: SummaryEdgeApplication, summaryFact: FinalFactAp) -> Set + handleSummaryEdge: (currentFactAp: FinalFactAp, summaryEffect: SummaryEdgeApplication, summaryEdge: SummaryEdge) -> Set ) { applyMethodAnySummaries( currentEdge, @@ -969,7 +972,7 @@ class NormalMethodAnalyzer( methodSummaries, { it.initialFactAp } ) { currentFactAp, summaryEdgeEffect, methodSummary -> - handleSummaryEdge(currentFactAp, summaryEdgeEffect, methodSummary.factAp) + handleSummaryEdge(currentFactAp, summaryEdgeEffect, methodSummary.summaryEdge()) } } @@ -1100,7 +1103,7 @@ class NormalMethodAnalyzer( summaryHandler.handleZeroToFact( currentEdgeFactAp, SummaryExclusionRefinement(ExclusionSet.Universe), - summaryEdge.factAp + summaryEdge.summaryEdge() ) } @@ -1110,7 +1113,7 @@ class NormalMethodAnalyzer( initialFact, currentEdgeFactAp, SummaryExclusionRefinement(initialFact.exclusions), - summaryEdge.factAp + summaryEdge.summaryEdge() ) } @@ -1119,7 +1122,7 @@ class NormalMethodAnalyzer( ndSummaryInitial, currentEdgeFactAp, SummaryExclusionRefinement(ExclusionSet.Universe), - summaryEdge.factAp + summaryEdge.summaryEdge() ) } } @@ -1133,7 +1136,7 @@ class NormalMethodAnalyzer( currentEdge.initialFactAp, currentEdgeFactAp, SummaryExclusionRefinement(currentEdge.initialFactAp.exclusions), - summaryEdge.factAp + summaryEdge.summaryEdge() ) } @@ -1142,7 +1145,7 @@ class NormalMethodAnalyzer( ndSummaryInitial, currentEdgeFactAp, SummaryExclusionRefinement(ExclusionSet.Universe), - summaryEdge.factAp + summaryEdge.summaryEdge() ) } } @@ -1153,7 +1156,7 @@ class NormalMethodAnalyzer( ndSummaryInitial + currentEdge.initialFacts, currentEdgeFactAp, SummaryExclusionRefinement(ExclusionSet.Universe), - summaryEdge.factAp + summaryEdge.summaryEdge() ) } } @@ -1231,6 +1234,9 @@ class NormalMethodAnalyzer( } } } + + private fun FactToFact.summaryEdge() = SummaryEdge.F2F(initialFactAp, factAp) + private fun NDFactToFact.summaryEdge() = SummaryEdge.NdF2F(initialFacts, factAp) } class EmptyMethodAnalyzer( diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/TaintAnalysisManager.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/TaintAnalysisManager.kt index 3983289..e24c9c8 100644 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/TaintAnalysisManager.kt +++ b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/TaintAnalysisManager.kt @@ -1,16 +1,18 @@ package org.seqra.dataflow.ap.ifds -import org.seqra.ir.api.common.CommonMethod -import org.seqra.ir.api.common.cfg.CommonInst -import org.seqra.util.analysis.ApplicationGraph import org.seqra.dataflow.ap.ifds.analysis.AnalysisManager import org.seqra.dataflow.ap.ifds.analysis.MethodAnalysisContext +import org.seqra.dataflow.ap.ifds.analysis.MethodCallResolver import org.seqra.dataflow.ap.ifds.taint.TaintAnalysisContext +import org.seqra.ir.api.common.CommonMethod +import org.seqra.ir.api.common.cfg.CommonInst +import org.seqra.util.analysis.ApplicationGraph interface TaintAnalysisManager : AnalysisManager { override fun getMethodAnalysisContext( methodEntryPoint: MethodEntryPoint, - graph: ApplicationGraph + graph: ApplicationGraph, + callResolver: MethodCallResolver, ): MethodAnalysisContext { error("Taint context required") } @@ -18,6 +20,7 @@ interface TaintAnalysisManager : AnalysisManager { fun getMethodAnalysisContext( methodEntryPoint: MethodEntryPoint, graph: ApplicationGraph, + callResolver: MethodCallResolver, taintAnalysisContext: TaintAnalysisContext, ): MethodAnalysisContext } diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/TaintAnalysisUnitRunnerManager.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/TaintAnalysisUnitRunnerManager.kt index 8e9001e..d0caa3e 100644 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/TaintAnalysisUnitRunnerManager.kt +++ b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/TaintAnalysisUnitRunnerManager.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.withTimeoutOrNull import mu.KotlinLogging import org.seqra.dataflow.ap.ifds.access.ApManager import org.seqra.dataflow.ap.ifds.analysis.MethodAnalysisContext +import org.seqra.dataflow.ap.ifds.analysis.MethodCallResolver import org.seqra.dataflow.ap.ifds.serialization.SummarySerializationContext import org.seqra.dataflow.ap.ifds.taint.TaintAnalysisContext import org.seqra.dataflow.ap.ifds.taint.TaintAnalysisUnitStorage @@ -331,9 +332,10 @@ class TaintAnalysisUnitRunnerManager( ) : TaintAnalysisManager by analysisManager { override fun getMethodAnalysisContext( methodEntryPoint: MethodEntryPoint, - graph: ApplicationGraph + graph: ApplicationGraph, + callResolver: MethodCallResolver ): MethodAnalysisContext = analysisManager.getMethodAnalysisContext( - methodEntryPoint, graph, + methodEntryPoint, graph, callResolver, TaintAnalysisContext(taintConfig, sinkTracker) ) } diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/FactAp.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/FactAp.kt index 781ebe0..ff29044 100644 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/FactAp.kt +++ b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/FactAp.kt @@ -63,6 +63,7 @@ interface FinalFactAp : FactAp, ReadableAccessorList { fun filterFact(filter: FactTypeChecker.FactApFilter): FinalFactAp? fun contains(factAp: InitialFactAp): Boolean + fun equalTo(factAp: InitialFactAp): Boolean fun hasEmptyDelta(other: InitialFactAp): Boolean = delta(other).any { it.isEmpty } diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/automata/AccessGraphFinalFactAp.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/automata/AccessGraphFinalFactAp.kt index 0fc12d2..c8b654c 100644 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/automata/AccessGraphFinalFactAp.kt +++ b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/automata/AccessGraphFinalFactAp.kt @@ -107,4 +107,11 @@ data class AccessGraphFinalFactAp( if (base != factAp.base) return false return access.containsAll(factAp.access) } + + override fun equalTo(factAp: InitialFactAp): Boolean { + factAp as AccessGraphInitialFactAp + + if (base != factAp.base) return false + return access == factAp.access + } } diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/cactus/AccessCactus.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/cactus/AccessCactus.kt index 93f4483..5f56271 100644 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/cactus/AccessCactus.kt +++ b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/cactus/AccessCactus.kt @@ -73,6 +73,10 @@ class AccessCactus( override fun contains(factAp: InitialFactAp): Boolean = this.delta(factAp).any { it.isEmpty } + override fun equalTo(factAp: InitialFactAp): Boolean { + TODO("Not yet implemented") + } + override fun getStartAccessors(): Set = access.allEdges.mapTo(hashSetOf()) { it.accessor } diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/tree/AccessTree.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/tree/AccessTree.kt index 7b76412..ec60d6b 100644 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/tree/AccessTree.kt +++ b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/tree/AccessTree.kt @@ -84,6 +84,29 @@ class AccessTree( return node.isAbstract } + override fun equalTo(factAp: InitialFactAp): Boolean { + factAp as AccessPath + + if (base != factAp.base) return false + + val otherAccess = factAp.access + if (otherAccess == null) { + return access.isEmptyAbstract + } + + var node = access + for (accessor in otherAccess) { + if (accessor == FinalAccessor) { + return node.isFinal && node.accessors == null + } + + if (node.accessors?.size != 1) return false + node = node.getChild(apManager, accessor) ?: return false + } + + return node.isEmptyAbstract + } + private sealed interface AccessTreeDelta : FinalFactAp.Delta data object EmptyAccessTreeDelta : AccessTreeDelta { @@ -273,6 +296,9 @@ class AccessTree( val isEmpty: Boolean get() = !isAbstract && !isFinal && accessors == null + val isEmptyAbstract: Boolean + get() = isAbstract && !isFinal && accessors == null + private fun accessorIndex(accessor: Accessor): Int { if (accessors == null) return -1 return accessors.binarySearch(accessor) diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/analysis/AnalysisManager.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/analysis/AnalysisManager.kt index 0ad68d6..f113af1 100644 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/analysis/AnalysisManager.kt +++ b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/analysis/AnalysisManager.kt @@ -24,7 +24,8 @@ interface AnalysisManager: LanguageManager { fun getMethodAnalysisContext( methodEntryPoint: MethodEntryPoint, - graph: ApplicationGraph + graph: ApplicationGraph, + callResolver: MethodCallResolver, ): MethodAnalysisContext fun getMethodCallResolver( diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/analysis/MethodCallSummaryHandler.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/analysis/MethodCallSummaryHandler.kt index 64f72af..1042c41 100644 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/analysis/MethodCallSummaryHandler.kt +++ b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/analysis/MethodCallSummaryHandler.kt @@ -14,6 +14,13 @@ import org.seqra.dataflow.ap.ifds.analysis.MethodSequentFlowFunction.TraceInfo interface MethodCallSummaryHandler { val factTypeChecker: FactTypeChecker + sealed interface SummaryEdge { + val final: FinalFactAp + + data class F2F(val initial: InitialFactAp, override val final: FinalFactAp) : SummaryEdge + data class NdF2F(val initial: Set, override val final: FinalFactAp) : SummaryEdge + } + fun mapMethodExitToReturnFlowFact(fact: FinalFactAp): List fun handleZeroToZero(summaryFact: FinalFactAp?): Set { @@ -28,11 +35,11 @@ interface MethodCallSummaryHandler { fun handleZeroToFact( currentFactAp: FinalFactAp, summaryEffect: SummaryEdgeApplication, - summaryFact: FinalFactAp + summaryEdge: SummaryEdge, ): Set = handleSummary( currentFactAp, summaryEffect, - summaryFact, + summaryEdge, createSideEffectRequirement = { check(it is ExclusionSet.Universe) { "Incorrect refinement" } null @@ -49,11 +56,11 @@ interface MethodCallSummaryHandler { initialFactAp: InitialFactAp, currentFactAp: FinalFactAp, summaryEffect: SummaryEdgeApplication, - summaryFact: FinalFactAp + summaryEdge: SummaryEdge, ): Set = handleSummary( currentFactAp, summaryEffect, - summaryFact, + summaryEdge, createSideEffectRequirement = { refinement -> Sequent.SideEffectRequirement(initialFactAp.refine(refinement)) } @@ -67,11 +74,11 @@ interface MethodCallSummaryHandler { initialFacts: Set, currentFactAp: FinalFactAp, summaryEffect: SummaryEdgeApplication, - summaryFact: FinalFactAp + summaryEdge: SummaryEdge, ): Set = handleSummary( currentFactAp, summaryEffect, - summaryFact, + summaryEdge, createSideEffectRequirement = { check(it is ExclusionSet.Universe) { "Incorrect refinement" } null @@ -96,11 +103,11 @@ interface MethodCallSummaryHandler { fun handleSummary( currentFactAp: FinalFactAp, summaryEffect: SummaryEdgeApplication, - summaryFact: FinalFactAp, + summaryEdge: SummaryEdge, createSideEffectRequirement: (refinement: ExclusionSet) -> Sequent?, handleSummaryEdge: (initialFactRefinement: ExclusionSet?, summaryFactAp: FinalFactAp) -> Sequent ): Set { - val mappedSummaryFacts = mapMethodExitToReturnFlowFact(summaryFact) + val mappedSummaryFacts = mapMethodExitToReturnFlowFact(summaryEdge.final) return when (summaryEffect) { is SummaryApRefinement -> mappedSummaryFacts.mapNotNullTo(hashSetOf()) { mappedSummaryFact -> diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/trace/MethodForwardTraceResolver.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/trace/MethodForwardTraceResolver.kt index 8092964..5f14311 100644 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/trace/MethodForwardTraceResolver.kt +++ b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/trace/MethodForwardTraceResolver.kt @@ -16,6 +16,7 @@ import org.seqra.dataflow.ap.ifds.analysis.AnalysisManager import org.seqra.dataflow.ap.ifds.analysis.MethodAnalysisContext import org.seqra.dataflow.ap.ifds.analysis.MethodCallFlowFunction import org.seqra.dataflow.ap.ifds.analysis.MethodCallFlowFunction.ZeroCallFact +import org.seqra.dataflow.ap.ifds.analysis.MethodCallSummaryHandler.SummaryEdge import org.seqra.dataflow.ap.ifds.analysis.MethodSequentFlowFunction import org.seqra.dataflow.ap.ifds.analysis.MethodSequentFlowFunction.Sequent import org.seqra.dataflow.graph.MethodInstGraph @@ -289,7 +290,7 @@ class MethodForwardTraceResolver( callerFact: FinalFactAp, methodInitialFactBase: AccessPathBase, methodSummaries: List, - handleSummaryEdge: (currentFactAp: FinalFactAp, summaryEffect: SummaryEdgeApplication, summaryFact: FinalFactAp) -> Set, + handleSummaryEdge: (currentFactAp: FinalFactAp, summaryEffect: SummaryEdgeApplication, summaryEdge: SummaryEdge) -> Set, ): Boolean { var summaryApplied = false val methodInitialFact = callerFact.rebase(methodInitialFactBase) @@ -302,7 +303,8 @@ class MethodForwardTraceResolver( for (summaryEdgeEffect in summaryEdgeEffects) { for (methodSummary in summaryEdges) { - val sf = handleSummaryEdge(callerFact, summaryEdgeEffect, methodSummary.factAp) + val summaryEdge = SummaryEdge.F2F(methodSummary.initialFactAp, methodSummary.factAp) + val sf = handleSummaryEdge(callerFact, summaryEdgeEffect, summaryEdge) handleSequentFact(currentEdge, sf) summaryApplied = true } diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/graph/CompactGraphSimulator.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/graph/CompactGraphSimulator.kt index c120841..b8d88b9 100644 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/graph/CompactGraphSimulator.kt +++ b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/graph/CompactGraphSimulator.kt @@ -14,9 +14,19 @@ inline fun simulateGraph( eval: (Int, State) -> State, ): Array { if (graph.size == 0) return emptyArray() - val statesAfter = arrayOfNulls(graph.size) + simulateGraph(statesAfter, graph, initialStmtIdx, initialState, merge, eval) + return statesAfter +} +inline fun simulateGraph( + statesAfter: Array, + graph: CompactGraph, + initialStmtIdx: Int, + initialState: State, + merge: (Int, Int2ObjectMap) -> State, + eval: (Int, State) -> State, +) { val topOrderComparator = CompactGraphTopSort(graph).graphTopOrderNodeComparator(initialStmtIdx) val enqueuedStmts = BitSet() @@ -61,6 +71,4 @@ inline fun simulateGraph( } } } - - return statesAfter } diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRCallResolver.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRCallResolver.kt index 74a8504..448f831 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRCallResolver.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRCallResolver.kt @@ -2,6 +2,12 @@ package org.seqra.dataflow.jvm.ap.ifds import kotlinx.coroutines.runBlocking import mu.KLogging +import org.seqra.dataflow.ap.ifds.EmptyMethodContext +import org.seqra.dataflow.ap.ifds.MethodContext +import org.seqra.dataflow.ap.ifds.MethodWithContext +import org.seqra.dataflow.ifds.UnknownUnit +import org.seqra.dataflow.jvm.ap.ifds.LambdaAnonymousClassFeature.JIRLambdaClass +import org.seqra.dataflow.jvm.ifds.JIRUnitResolver import org.seqra.ir.api.jvm.JIRClassOrInterface import org.seqra.ir.api.jvm.JIRClasspath import org.seqra.ir.api.jvm.JIRMethod @@ -20,12 +26,6 @@ import org.seqra.ir.api.jvm.cfg.JIRVirtualCallExpr import org.seqra.ir.api.jvm.ext.isSubClassOf import org.seqra.ir.impl.features.hierarchyExt import org.seqra.jvm.graph.JApplicationGraph -import org.seqra.dataflow.ap.ifds.EmptyMethodContext -import org.seqra.dataflow.ap.ifds.MethodContext -import org.seqra.dataflow.ap.ifds.MethodWithContext -import org.seqra.dataflow.ifds.UnknownUnit -import org.seqra.dataflow.jvm.ap.ifds.LambdaAnonymousClassFeature.JIRLambdaClass -import org.seqra.dataflow.jvm.ifds.JIRUnitResolver import java.util.Optional import java.util.concurrent.ConcurrentHashMap import kotlin.jvm.optionals.getOrNull @@ -54,6 +54,20 @@ class JIRCallResolver( data class Lambda(val instance: JIRVirtualCallExpr, val method: JIRMethod) : MethodResolutionResult } + fun allKnownOverridesOrNull(method: JIRMethod): List? { + if (unitResolver.resolve(method) == UnknownUnit) return null + + val methods = mutableListOf() + if (!method.isAbstract) { + methods.add(method) + } + + hierarchy.findOverrides(method, includeAbstract = false) + .filterTo(methods) { unitResolver.resolve(it) != UnknownUnit } + + return methods + } + fun resolve(call: JIRCallExpr, location: JIRInst, context: MethodContext): List { val method = call.method.method val methodIgnored = unitResolver.resolve(method) == UnknownUnit diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt index fddb8ee..af0cd96 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt @@ -3,6 +3,7 @@ package org.seqra.dataflow.jvm.ap.ifds import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import org.seqra.dataflow.ap.ifds.AccessPathBase import org.seqra.dataflow.jvm.ap.ifds.alias.JIRIntraProcAliasAnalysis +import org.seqra.dataflow.jvm.ap.ifds.analysis.JIRMethodCallResolver import org.seqra.ir.api.common.cfg.CommonInst import org.seqra.ir.api.jvm.cfg.JIRInst import org.seqra.jvm.graph.JApplicationGraph @@ -10,8 +11,15 @@ import org.seqra.jvm.graph.JApplicationGraph class JIRLocalAliasAnalysis( private val entryPoint: JIRInst, private val graph: JApplicationGraph, - private val languageManager: JIRLanguageManager + private val callResolver: JIRMethodCallResolver, + private val languageManager: JIRLanguageManager, + private val params: Params, ) { + data class Params( + val useAliasAnalysis: Boolean = true, + val aliasAnalysisInterProcCallDepth: Int = 0, + ) + private val aliasInfo by lazy { compute() } class MethodAliasInfo( @@ -38,7 +46,7 @@ class JIRLocalAliasAnalysis( } private fun compute(): MethodAliasInfo { - return JIRIntraProcAliasAnalysis(entryPoint, graph, languageManager).compute() + return JIRIntraProcAliasAnalysis(entryPoint, graph, callResolver, languageManager, params).compute() } sealed interface AliasAccessor { diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt index 564d821..782f51c 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt @@ -1,12 +1,18 @@ package org.seqra.dataflow.jvm.ap.ifds.alias import it.unimi.dsi.fastutil.ints.Int2ObjectMap +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import org.seqra.dataflow.jvm.ap.ifds.alias.JIRIntraProcAliasAnalysis.JIRInstGraph import org.seqra.dataflow.jvm.ap.ifds.alias.RefValue.Local import org.seqra.ir.api.jvm.JIRField +import org.seqra.ir.api.jvm.JIRMethod import org.seqra.ir.api.jvm.cfg.JIRInst +import org.seqra.ir.api.jvm.cfg.JIRReturnInst import java.util.BitSet -class DSUAliasAnalysis { +class DSUAliasAnalysis( + val methodCallResolver: CallResolver, +) { companion object { private const val HEAP_CHAIN_LIMIT = 5 } @@ -20,6 +26,22 @@ class DSUAliasAnalysis { val statesAfterStmt: List ) + class GraphAnalysisState(size: Int, val call: CallTreeNode) { + val stateBeforeStmt = arrayOfNulls(size) + val stateAfterStmt = arrayOfNulls(size) + } + + class ResolvedCallMethod( + val graph: JIRInstGraph, + val state: GraphAnalysisState + ) + + private object RootInstEvalContext : InstEvalContext { + override fun createThis(): RefValue = RefValue.This + override fun createArg(idx: Int): RefValue = RefValue.Arg(idx) + override fun createLocal(idx: Int): Local = Local(idx, level = 0, ctx = 0) + } + data class ImmutableState(val aliasGroups: ImmutableIntDSU) { fun mutableCopy(): State = State(aliasGroups.mutableCopy()) } @@ -42,16 +64,32 @@ class DSUAliasAnalysis { } ?: ConnectedAliases(emptyList()) } - fun analyze(jig: JIRIntraProcAliasAnalysis.JIRInstGraph): AnalysisResult { + fun analyze(jig: JIRInstGraph): AnalysisResult { val initialState = ImmutableState(IntDisjointSets()) - val stateBeforeStmt = arrayOfNulls(jig.statements.size) - val stateAfterStmt = simulateJIG(jig, initialState, stateBeforeStmt, ::eval, ::merge) + val rootCall = CallTreeNode(level = 0, instEvalCtx = RootInstEvalContext) + val analysisState = GraphAnalysisState(jig.statements.size, rootCall) + val (stateBeforeStmt, stateAfterStmt) = analyze(jig, initialState, analysisState) return AnalysisResult( getConnectedAliases(stateBeforeStmt), getConnectedAliases(stateAfterStmt) ) } + private fun analyze( + jig: JIRInstGraph, + initialState: ImmutableState, + analysisState: GraphAnalysisState + ): Pair, Array> { + val stateBeforeStmt = analysisState.stateBeforeStmt + val stateAfterStmt = analysisState.stateAfterStmt + simulateJIG( + jig, initialState, stateBeforeStmt, stateAfterStmt, + { i, s -> eval(i, s, analysisState.call) }, + ::merge + ) + return stateBeforeStmt to stateAfterStmt + } + private fun merge(states: Int2ObjectMap): ImmutableState { val statesWithPhi = states.mapValuesTo(hashMapOf()) { it.value?.mutableCopy() } val result = IntDisjointSets() @@ -60,8 +98,8 @@ class DSUAliasAnalysis { } sealed interface AAInfo - data class Unknown(val stmt: Stmt) : AAInfo - data class CallReturn(val stmt: Stmt.Call) : AAInfo + data class Unknown(val stmt: Stmt, val level: Int) : AAInfo + data class CallReturn(val stmt: Stmt.Call, val level: Int) : AAInfo sealed interface LocalAlias : AAInfo { data class SimpleLoc(val loc: RefValue) : LocalAlias @@ -88,20 +126,27 @@ class DSUAliasAnalysis { override val isImmutable: Boolean, ) : HeapAlias - private fun eval(inst: JIRInst, state: ImmutableState): ImmutableState = - eval(inst, state.mutableCopy()).asImmutable() + private fun eval(inst: JIRInst, state: ImmutableState, callFrame: CallTreeNode): ImmutableState = + eval(inst, state.mutableCopy(), callFrame).asImmutable() - private fun eval(inst: JIRInst, state: State): State { - val stmt = evalInst(inst) ?: return state + private fun eval(inst: JIRInst, state: State, callFrame: CallTreeNode): State { + val stmt = callFrame.instEvalCtx.evalInst(inst) ?: return state return when (stmt) { - is Stmt.Call -> evalCall(stmt, state) - is Stmt.NoCall -> evalSimple(stmt, state) + is Stmt.Call -> evalCall(stmt, state, callFrame) + is Stmt.NoCall -> evalSimple(stmt, callFrame, state) } } - private fun evalCall(stmt: Stmt.Call, state: State): State { + private fun evalCall(stmt: Stmt.Call, state: State, callFrame: CallTreeNode): State { + // todo: use instance alloc info + val resolvedCall = callFrame.resolveCall(stmt, methodCallResolver) + if (resolvedCall != null) { + val result = evalCall(stmt, state, callFrame, resolvedCall) + if (result != null) return result + } + val resultState = if (stmt.lValue != null) { - val info = aliasSetFromInfo(CallReturn(stmt)) + val info = aliasSetFromInfo(CallReturn(stmt, callFrame.level)) state.removeOldAndMergeWith(stmt.lValue.aliasInfo().index(), setOf(info)) } else state if (stmt.cantMutateAliasedHeap()) return resultState @@ -115,6 +160,37 @@ class DSUAliasAnalysis { return resultState.invalidateOuterHeapAliases(argAliases) } + private fun evalCall( + stmt: Stmt.Call, + state: State, + callFrame: CallTreeNode, + methods: Map + ): State? { + val stateBefore = state.asImmutable() + val statesAfterCall = mutableListOf() + + for ((_, resolvedMethod) in methods) { + analyze(resolvedMethod.graph, stateBefore, resolvedMethod.state) + + val methodFinalStates = resolvedMethod.state.mapCallFinalStates( + resolvedMethod.graph, stmt, callFrame.level + ) + statesAfterCall += methodFinalStates + } + + if (statesAfterCall.isEmpty()) return null + + if (statesAfterCall.size == 1) { + return statesAfterCall.first().mutableCopy() + } + + val statesMap = Int2ObjectOpenHashMap() + statesAfterCall.forEachIndexed { index, state -> + statesMap[index] = state + } + return merge(statesMap).mutableCopy() + } + private fun State.invalidateOuterHeapAliases(startInvalidAliases: Set): State { val invalidAliases = collectTransitiveInvalidAliases(startInvalidAliases) return resetHeapWithInvalidInstance(invalidAliases) @@ -162,21 +238,17 @@ class DSUAliasAnalysis { is CallReturn -> return true is HeapAlias -> if (aInfo.instance.index() in invalid) return true is LocalAlias.Alloc -> continue - is LocalAlias.SimpleLoc -> when (aInfo.loc) { - is Local -> if (aInfoIndex in invalid) return true - - // outer - is RefValue.Arg, - is RefValue.Static, - is RefValue.This -> return true + is LocalAlias.SimpleLoc -> { + if (aInfo.loc.isOuter()) return true + if (aInfoIndex in invalid) return true } } } return false } - private fun evalSimple(stmt: Stmt.NoCall, state: State): State = when (stmt) { - is Stmt.Assign -> evalAssign(stmt, state) + private fun evalSimple(stmt: Stmt.NoCall, callFrame: CallTreeNode, state: State): State = when (stmt) { + is Stmt.Assign -> evalAssign(stmt, callFrame, state) is Stmt.Copy -> evalCopy(stmt, state) @@ -190,29 +262,30 @@ class DSUAliasAnalysis { is Stmt.WriteStatic -> state } - private fun evalAssign(stmt: Stmt.Assign, state: State): State { - val rValue = evalExpr(stmt.expr, stmt, state) + private fun evalAssign(stmt: Stmt.Assign, callFrame: CallTreeNode, state: State): State { + val rValue = evalExpr(stmt.expr, stmt, callFrame, state) return state.removeOldAndMergeWith(stmt.lValue.aliasInfo().index(), rValue) } private fun evalCopy(stmt: Stmt.Copy, state: State): State = state.removeOldAndMergeWith(stmt.lValue.aliasInfo(), stmt.rValue.aliasInfo()) - private fun evalExpr(expr: Expr, stmt: Stmt, state: State): Set = when (expr) { + private fun evalExpr(expr: Expr, stmt: Stmt, callFrame: CallTreeNode, state: State): Set = when (expr) { is Expr.Alloc, is SimpleValue.RefConst -> setOf(aliasSetFromInfo(LocalAlias.Alloc(stmt))) - is Expr.FieldLoad -> evalFieldLoad(expr, stmt, state) + is Expr.FieldLoad -> evalFieldLoad(expr, stmt, callFrame, state) - is Expr.ArrayLoad -> evalArrayLoad(expr, stmt, state) + is Expr.ArrayLoad -> evalArrayLoad(expr, stmt, callFrame, state) is SimpleValue.Primitive, - is Expr.Unknown -> setOf(aliasSetFromInfo(Unknown(stmt))) + is Expr.Unknown -> setOf(aliasSetFromInfo(Unknown(stmt, callFrame.level))) } private fun evalHeapLoad( instance: RefValue, - stmt: Stmt, state: State, heapAppender: (AAInfo) -> HeapAlias? + stmt: Stmt, callFrame: CallTreeNode, + state: State, heapAppender: (AAInfo) -> HeapAlias? ): Set { var valueMayBeUnknown = false @@ -228,13 +301,13 @@ class DSUAliasAnalysis { } if (valueMayBeUnknown) { - result += aliasSetFromInfo(Unknown(stmt)) + result += aliasSetFromInfo(Unknown(stmt, callFrame.level)) } return result } - private fun createHeapAliasWrtLimit(instance: AAInfo, builder: (Int) -> T): T? { + private fun createHeapAliasWrtLimit(instance: AAInfo, builder: (Int) -> T): T? { val depth = if (instance is HeapAlias) instance.depth + 1 else 0 if (depth > HEAP_CHAIN_LIMIT) return null return builder(depth) @@ -253,15 +326,15 @@ class DSUAliasAnalysis { private fun evalArrayLoad( load: Expr.ArrayLoad, - stmt: Stmt, state: State + stmt: Stmt, callFrame: CallTreeNode, state: State ): Set = - evalHeapLoad(load.instance, stmt, state, ::createArrayAliasWrtLimit) + evalHeapLoad(load.instance, stmt, callFrame, state, ::createArrayAliasWrtLimit) private fun evalFieldLoad( load: Expr.FieldLoad, - stmt: Stmt, state: State + stmt: Stmt, callFrame: CallTreeNode, state: State ): Set = - evalHeapLoad(load.instance, stmt, state) { instance -> createFieldAliasWrtLimit(instance, load.field) } + evalHeapLoad(load.instance, stmt, callFrame, state) { instance -> createFieldAliasWrtLimit(instance, load.field) } private fun evalHeapStore( instance: RefValue, @@ -325,13 +398,9 @@ class DSUAliasAnalysis { is LocalAlias.Alloc -> concrete++ - is LocalAlias.SimpleLoc -> when (info.loc) { - is Local -> continue - - // outer - is RefValue.Arg, - is RefValue.Static, - is RefValue.This -> return true + is LocalAlias.SimpleLoc -> { + if (info.loc.isOuter()) return true + continue } is Unknown -> return true @@ -340,6 +409,121 @@ class DSUAliasAnalysis { return concrete > 1 } + private fun RefValue.isOuter(): Boolean = when (this) { + is Local -> false + is RefValue.Arg, + is RefValue.Static, + is RefValue.This -> true + } + + private fun GraphAnalysisState.mapCallFinalStates( + graph: JIRInstGraph, callStmt: Stmt.Call, level: Int + ): List = + graph.statements.filterIsInstance().mapNotNull { inst -> + val stmt = call.instEvalCtx.evalInst(inst) as Stmt.Return + val finalState = stateAfterStmt[stmt.originalIdx] + ?: return@mapNotNull null + + finalState.createStateAfterCall(callStmt, stmt.value, level) + } + + private fun ImmutableState.createStateAfterCall(stmt: Stmt.Call, retVal: RefValue?, level: Int): ImmutableState { + var state = mutableCopy() + stmt.lValue?.let { v -> + val retVal = retVal?.aliasInfo() ?: return@let + val outerRetVal = v.aliasInfo() + state = state.removeOldAndMergeWith(outerRetVal, retVal) + } + + val result = state.removeCallLocals(level) + return result.asImmutable() + } + + private fun State.removeCallLocals(level: Int): State { + val result = aliasGroups.mutableCopy() + val aaInfoToRemove = BitSet() + + for (aliasSet in aliasGroups.allSets()) { + for (info in aliasSet) { + val element = aliasManager.getElementUncheck(info) + if (!element.isCallLocal(level)) continue + + aaInfoToRemove.set(info) + + val alternative = element.nonLocalAlternative(aliasGroups, level) + ?: continue + + val alternativeId = aliasManager.getOrAdd(alternative) + aliasSet.forEach { result.union(it, alternativeId) } + } + } + + result.removeAll { aaInfoToRemove.get(it) } + return State(result) + } + + private fun AAInfo.isCallLocal(level: Int): Boolean = when (this) { + is LocalAlias.Alloc -> false + is Unknown -> this.level > level + is CallReturn -> this.level > level + is LocalAlias.SimpleLoc -> loc is Local && loc.level > level + is HeapAlias -> instance.isCallLocal(level) + } + + private fun AAInfo.nonLocalAlternative(aliasGroups: IntDisjointSets, level: Int): AAInfo? { + if (this !is HeapAlias) return null + return findNonLocalAlternative(aliasGroups, level) + } + + private fun AAInfo.findNonLocalAlternative(aliasGroups: IntDisjointSets, level: Int): AAInfo? { + return when (this) { + is Unknown, is CallReturn -> null + is LocalAlias.Alloc -> this + + is LocalAlias.SimpleLoc -> { + if (loc !is Local) return this + if (loc.level <= level) return this + + val alternatives = aliasGroups.findAlternatives(this) + .filterTo(mutableListOf()) { !it.isCallLocal(level) } + + alternatives.sortBy { it.alternativePriority() } + + alternatives.firstNotNullOfOrNull { it.findNonLocalAlternative(aliasGroups, level) } + } + + is HeapAlias -> { + val instanceAlternative = instance.findNonLocalAlternative(aliasGroups, level) + ?: return null + + when (this) { + is ArrayAlias -> createArrayAliasWrtLimit(instanceAlternative) + is FieldAlias -> createFieldAliasWrtLimit(instanceAlternative, field) + } + } + } + } + + private fun AAInfo.alternativePriority(): Int = when (this) { + is LocalAlias.Alloc -> 0 + is LocalAlias.SimpleLoc -> 1 + is FieldAlias -> 2 + this.depth + is ArrayAlias -> 1000 + this.depth + else -> 10_000 + } + + private fun IntDisjointSets.findAlternatives(element: AAInfo): List { + val elementId = aliasManager.getOrAdd(element) + val alternatives = mutableListOf() + forEachElementInSet(elementId) { alternativeId -> + if (alternativeId == elementId) return@forEachElementInSet + + val alternative = aliasManager.getElementUncheck(alternativeId) + alternatives.add(alternative) + } + return alternatives + } + private fun RefValue.aliasInfo(): AAInfo = LocalAlias.SimpleLoc(this) private fun Value.aliasInfo(): AAInfo? = when (this) { diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/InterProcCallNode.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/InterProcCallNode.kt new file mode 100644 index 0000000..2784a78 --- /dev/null +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/InterProcCallNode.kt @@ -0,0 +1,98 @@ +package org.seqra.dataflow.jvm.ap.ifds.alias + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis +import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.GraphAnalysisState +import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.ResolvedCallMethod +import org.seqra.dataflow.jvm.ap.ifds.alias.JIRIntraProcAliasAnalysis.JIRInstGraph +import org.seqra.dataflow.jvm.ap.ifds.alias.RefValue.Local +import org.seqra.dataflow.jvm.ap.ifds.analysis.JIRMethodCallResolver +import org.seqra.ir.api.jvm.JIRMethod +import org.seqra.ir.api.jvm.cfg.JIRInst +import org.seqra.jvm.graph.JApplicationGraph +import java.util.BitSet + +interface CallResolver { + fun resolveMethodCall(callStmt: Stmt.Call, level: Int): List? + fun buildMethodGraph(method: JIRMethod): JIRInstGraph? +} + +abstract class JirCallResolver( + val methodCallResolver: JIRMethodCallResolver, + val graph: JApplicationGraph, + val params: JIRLocalAliasAnalysis.Params +): CallResolver { + val callResolver = methodCallResolver.callResolver + + abstract fun buildMethodJig(entryPoint: JIRInst): JIRInstGraph + + override fun resolveMethodCall(callStmt: Stmt.Call, level: Int): List? { + if (level >= params.aliasAnalysisInterProcCallDepth) return null + + val methods = callResolver.allKnownOverridesOrNull(callStmt.method) + ?: return null + + return methods.takeIf { it.isNotEmpty() } + } + + override fun buildMethodGraph(method: JIRMethod): JIRInstGraph? { + val entryPoint = graph.methodGraph(method).entryPoints().singleOrNull() + ?: return null + + return buildMethodJig(entryPoint) + } +} + +class CallTreeNode(val level: Int, val instEvalCtx: InstEvalContext) { + private val emptyCalls = BitSet() + private val calls = Int2ObjectOpenHashMap() + + fun resolveCall(stmt: Stmt.Call, callResolver: CallResolver): Map? { + if (emptyCalls.get(stmt.originalIdx)) return ResolvedCall.empty.methods + + return calls.getOrPut(stmt.originalIdx) { + val resolved = resolveCallNoCache(stmt, level, callResolver) + if (resolved === ResolvedCall.empty) { + emptyCalls.set(stmt.originalIdx) + return ResolvedCall.empty.methods + } + + resolved + }.methods + } +} + +private class ResolvedCall(val methods: Map?) { + companion object { + val empty = ResolvedCall(methods = null) + } +} + +private class NestedCallInstEvalCtx(val call: Stmt.Call, val level: Int) : InstEvalContext { + override fun createArg(idx: Int): Value = call.args.getOrNull(idx) + ?: error("Incorrect argument idx: $idx") + + override fun createThis(): Value = call.instance + ?: error("Non instance call") + + override fun createLocal(idx: Int): Local = Local(idx, level = level, ctx = call.originalIdx) +} + +private fun resolveCallNoCache(stmt: Stmt.Call, level: Int, callResolver: CallResolver): ResolvedCall { + val methods = callResolver.resolveMethodCall(stmt, level) + ?: return ResolvedCall.empty + + val resolvedCall = methods.mapNotNull { method -> + val graph = callResolver.buildMethodGraph(method) + ?: return@mapNotNull null + + val nextLevel = level + 1 + val instEvalCtx = NestedCallInstEvalCtx(stmt, nextLevel) + val state = GraphAnalysisState(graph.statements.size, CallTreeNode(nextLevel, instEvalCtx)) + method to ResolvedCallMethod(graph, state) + }.toMap() + + if (resolvedCall.isEmpty()) return ResolvedCall.empty + + return ResolvedCall(resolvedCall) +} diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt index 8b5a59d..e5712c6 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt @@ -15,6 +15,7 @@ import org.seqra.ir.api.jvm.cfg.JIRExpr import org.seqra.ir.api.jvm.cfg.JIRFieldRef import org.seqra.ir.api.jvm.cfg.JIRImmediate import org.seqra.ir.api.jvm.cfg.JIRInst +import org.seqra.ir.api.jvm.cfg.JIRInstanceCallExpr import org.seqra.ir.api.jvm.cfg.JIRLocalVar import org.seqra.ir.api.jvm.cfg.JIRNewArrayExpr import org.seqra.ir.api.jvm.cfg.JIRNewExpr @@ -29,10 +30,10 @@ sealed interface ExprOrValue sealed interface Value: ExprOrValue sealed interface RefValue: Value { - data class Local(val idx: Int) : RefValue + data class Local(val idx: Int, val level: Int, val ctx: Int) : RefValue data class Arg(val idx: Int) : RefValue - data class Static(val type: String) : RefValue data object This : RefValue + data class Static(val type: String) : RefValue } sealed interface Expr: ExprOrValue { @@ -52,7 +53,7 @@ sealed interface Stmt { sealed interface NoCall: Stmt - data class Call(val method: JIRMethod, val lValue: RefValue.Local?, 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 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 @@ -63,7 +64,13 @@ sealed interface Stmt { data class Throw(val value: RefValue, override val originalIdx: Int) : NoCall } -fun evalInst(inst: JIRInst): Stmt? { +interface InstEvalContext { + fun createThis(): Value + fun createArg(idx: Int): Value + fun createLocal(idx: Int): RefValue.Local +} + +fun InstEvalContext.evalInst(inst: JIRInst): Stmt? { return when (inst) { is JIRAssignInst -> { evalAssign(inst.lhv, inst.rhv, inst) @@ -77,28 +84,25 @@ fun evalInst(inst: JIRInst): Stmt? { val local = inst.throwable as? JIRLocalVar ?: return null val expr = Expr.Alloc(inst) - val lValue = RefValue.Local(local.index) - val stmt = Stmt.Assign(lValue, expr, inst.location.index) - return stmt + val lValue = createLocal(local.index) + Stmt.Assign(lValue, expr, inst.location.index) } is JIRReturnInst -> { val value = inst.returnValue?.let { evalSimpleValue(it as JIRImmediate) } as? RefValue - val stmt = Stmt.Return(value, inst.location.index) - return stmt + Stmt.Return(value, inst.location.index) } is JIRThrowInst -> { - val value = getLocalRefValue(inst.throwable) - val stmt = Stmt.Throw(value, inst.location.index) - return stmt + val value = getLocalRefValue(inst.throwable) as? RefValue ?: return null + Stmt.Throw(value, inst.location.index) } else -> null } } -private fun evalAssign(lhv: JIRValue, rhv: JIRExpr, inst: JIRInst): Stmt? { +private fun InstEvalContext.evalAssign(lhv: JIRValue, rhv: JIRExpr, inst: JIRInst): Stmt? { val loc = inst.location val isPrimitive = lhv.type is JIRPrimitiveType if (rhv is JIRCallExpr) { @@ -112,7 +116,7 @@ private fun evalAssign(lhv: JIRValue, rhv: JIRExpr, inst: JIRInst): Stmt? { when (lhv) { is JIRLocalVar -> { - val lValue = RefValue.Local(lhv.index) + val lValue = createLocal(lhv.index) val stmt = when (expr) { is Expr -> Stmt.Assign(lValue, expr, loc.index) is RefValue -> Stmt.Copy(lValue, expr, loc.index) @@ -124,12 +128,12 @@ private fun evalAssign(lhv: JIRValue, rhv: JIRExpr, inst: JIRInst): Stmt? { when (lhv) { is JIRFieldRef -> { val instance = lhv.instance ?: return Stmt.WriteStatic(lhv.field.field, expr, loc.index) - val iv = getLocalRefValue(instance) + val iv = getLocalRefValue(instance) as? RefValue ?: return null return Stmt.FieldStore(iv, lhv.field.field, expr, loc.index) } is JIRArrayAccess -> { - val iv = getLocalRefValue(lhv.array) + val iv = getLocalRefValue(lhv.array) as? RefValue ?: return null return Stmt.ArrayStore(iv, SimpleValue.Primitive, expr, loc.index) } @@ -141,18 +145,19 @@ private fun evalAssign(lhv: JIRValue, rhv: JIRExpr, inst: JIRInst): Stmt? { } } -private fun evalCall( +private fun InstEvalContext.evalCall( expr: JIRCallExpr, loc: JIRInst, lValue: JIRValue?, ): Stmt { val args = expr.args.map { evalSimpleValue(it as JIRImmediate) } - val lhs = (lValue as? JIRLocalVar)?.let { RefValue.Local(it.index) } - val stmt = Stmt.Call(expr.method.method, lhs, args, loc.location.index) + val lhs = (lValue as? JIRLocalVar)?.let { createLocal(it.index) } + val instance = (expr as? JIRInstanceCallExpr)?.instance?.let { evalSimpleValue(it as JIRImmediate) } + val stmt = Stmt.Call(expr.method.method, lhs, instance, args, loc.location.index) return stmt } -private fun evalExpr(expr: JIRExpr, inst: JIRInst): ExprOrValue = when (expr) { +private fun InstEvalContext.evalExpr(expr: JIRExpr, inst: JIRInst): ExprOrValue = when (expr) { is JIRNewExpr, is JIRNewArrayExpr -> Expr.Alloc(inst) is JIRCastExpr -> evalExpr(expr.operand, inst) @@ -160,13 +165,13 @@ private fun evalExpr(expr: JIRExpr, inst: JIRInst): ExprOrValue = when (expr) { else -> Expr.Unknown } -private fun evalValue(value: JIRValue, loc: JIRInst): ExprOrValue = when (value) { +private fun InstEvalContext.evalValue(value: JIRValue, loc: JIRInst): ExprOrValue = when (value) { is JIRImmediate -> evalSimpleValue(value) is JIRRef -> evalRefValue(value, loc) else -> Expr.Unknown } -private fun evalSimpleValue(value: JIRImmediate): Value { +private fun InstEvalContext.evalSimpleValue(value: JIRImmediate): Value { if (value.type is JIRPrimitiveType) return SimpleValue.Primitive return when (value) { is JIRConstant -> SimpleValue.RefConst(value) @@ -178,7 +183,7 @@ private fun evalSimpleValue(value: JIRImmediate): Value { } } -private fun evalRefValue(value: JIRRef, loc: JIRInst): ExprOrValue { +private fun InstEvalContext.evalRefValue(value: JIRRef, loc: JIRInst): ExprOrValue { return when (value) { is JIRFieldRef -> { if (value.field.isStatic) { @@ -187,14 +192,14 @@ private fun evalRefValue(value: JIRRef, loc: JIRInst): ExprOrValue { } else { val instance = value.instance ?: return Expr.Alloc(loc) - val iv = getLocalRefValue(instance) + val iv = getLocalRefValue(instance) as? RefValue ?: return Expr.Unknown Expr.FieldLoad(iv, value.field.field) } } is JIRArrayAccess -> { val instance = value.array - val iv = getLocalRefValue(instance) + val iv = getLocalRefValue(instance) as? RefValue ?: return Expr.Unknown Expr.ArrayLoad(iv, SimpleValue.Primitive) } @@ -202,9 +207,9 @@ private fun evalRefValue(value: JIRRef, loc: JIRInst): ExprOrValue { } } -private fun getLocalRefValue(local: JIRValue): RefValue = when (local) { - is JIRThis -> RefValue.This - is JIRArgument -> RefValue.Arg(local.index) - is JIRLocalVar -> RefValue.Local(local.index) +private fun InstEvalContext.getLocalRefValue(local: JIRValue): Value = when (local) { + is JIRThis -> createThis() + is JIRArgument -> createArg(local.index) + is JIRLocalVar -> createLocal(local.index) else -> error("Unexpected local: $local") } diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRDSUAASimulator.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRDSUAASimulator.kt index 3edce4b..c2eb23e 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRDSUAASimulator.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRDSUAASimulator.kt @@ -8,9 +8,11 @@ inline fun simulateJIG( jig: JIRIntraProcAliasAnalysis.JIRInstGraph, initialState: State, statesBefore: Array, + statesAfter: Array, eval: (JIRInst, State) -> State, merge: (Int2ObjectMap) -> State, ) = simulateGraph( + statesAfter = statesAfter, graph = jig.graph, initialStmtIdx = jig.initialIdx, initialState = initialState, diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt index ac25f0d..56cc6f8 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt @@ -8,14 +8,15 @@ import org.seqra.dataflow.jvm.ap.ifds.JIRLanguageManager import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasInfo import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.AAInfo -import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.ConnectedAliases +import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.ArrayAlias import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.CallReturn +import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.ConnectedAliases import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.FieldAlias -import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.ArrayAlias +import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.HeapAlias import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.LocalAlias import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.Unknown -import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.HeapAlias import org.seqra.dataflow.jvm.ap.ifds.alias.RefValue.Local +import org.seqra.dataflow.jvm.ap.ifds.analysis.JIRMethodCallResolver import org.seqra.ir.api.common.CommonMethod import org.seqra.ir.api.common.cfg.CommonInst import org.seqra.ir.api.jvm.cfg.JIRInst @@ -26,7 +27,9 @@ import org.seqra.util.analysis.ApplicationGraph class JIRIntraProcAliasAnalysis( private val entryPoint: JIRInst, private val graph: JApplicationGraph, - private val languageManager: JIRLanguageManager + private val callResolver: JIRMethodCallResolver, + private val languageManager: JIRLanguageManager, + private val params: JIRLocalAliasAnalysis.Params, ) { data class JIRInstGraph( val statements: List, @@ -34,13 +37,14 @@ class JIRIntraProcAliasAnalysis( val initialIdx: Int, ) - private fun getJIG(): JIRInstGraph { + private fun getJIG(entryPoint: JIRInst): JIRInstGraph { @Suppress("UNCHECKED_CAST") val instGraph = MethodInstGraph.build( languageManager, graph as ApplicationGraph, entryPoint.location.method ) + return JIRInstGraph( statements = instGraph.instructions.map { it as JIRInst }, graph = instGraph.graph, @@ -48,9 +52,13 @@ class JIRIntraProcAliasAnalysis( ) } + private inner class CallResolver: JirCallResolver(callResolver, graph, params) { + override fun buildMethodJig(entryPoint: JIRInst): JIRInstGraph = getJIG(entryPoint) + } + fun compute(): JIRLocalAliasAnalysis.MethodAliasInfo { - val jig = getJIG() - val daa = DSUAliasAnalysis().analyze(jig) + val jig = getJIG(entryPoint) + val daa = DSUAliasAnalysis(CallResolver()).analyze(jig) val aliasBeforeStatement = Array(jig.statements.size) { i -> resolveLocalVar(daa.statesBeforeStmt[i]) }.also { squash(it) } diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt index 180042a..4ac1a18 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt @@ -1,7 +1,5 @@ package org.seqra.dataflow.jvm.ap.ifds.analysis -import org.seqra.ir.api.jvm.cfg.JIRInst -import org.seqra.ir.api.jvm.cfg.locals import org.seqra.dataflow.ap.ifds.AccessPathBase import org.seqra.dataflow.ap.ifds.Accessor import org.seqra.dataflow.ap.ifds.ElementAccessor @@ -12,6 +10,8 @@ import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAccessor import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasInfo import org.seqra.dataflow.jvm.ap.ifds.MethodFlowFunctionUtils +import org.seqra.ir.api.jvm.cfg.JIRInst +import org.seqra.ir.api.jvm.cfg.locals fun JIRLocalAliasAnalysis.forEachAliasAtStatement(statement: JIRInst, fact: FinalFactAp, body: (FinalFactAp) -> Unit) { val base = fact.base as? AccessPathBase.LocalVar ?: return @@ -27,6 +27,16 @@ fun JIRLocalAliasAnalysis.forEachAliasAfterStatement(statement: JIRInst, fact: F .forEach { alias -> applyAlias(fact, alias, body) } } +fun JIRLocalAliasAnalysis.forEachAliasAfterCallStatement(statement: JIRInst, fact: FinalFactAp, body: (FinalFactAp) -> Unit) { + val base = fact.base as? AccessPathBase.LocalVar ?: return + val aliasesBefore = findAlias(base, statement) ?: return + val aliasesAfter = findAliasAfterStatement(base, statement)?.toSet() ?: return + val aliasesPersistedThroughCall = aliasesBefore.filter { it in aliasesAfter } + + aliasesPersistedThroughCall.filterNot { alias -> alias.base is AccessPathBase.Constant } + .forEach { alias -> applyAlias(fact, alias, body) } +} + private fun applyAlias(fact: FinalFactAp, alias: AliasInfo, body: (FinalFactAp) -> Unit) { val result = alias.accessors.foldRight(fact.rebase(alias.base)) { accessor, f -> val apAccessor = accessor.apAccessor() diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRAnalysisManager.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRAnalysisManager.kt index 28464e2..cbf4d7a 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRAnalysisManager.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRAnalysisManager.kt @@ -10,6 +10,7 @@ import org.seqra.dataflow.ap.ifds.access.ApManager import org.seqra.dataflow.ap.ifds.access.FinalFactAp import org.seqra.dataflow.ap.ifds.analysis.MethodAnalysisContext import org.seqra.dataflow.ap.ifds.analysis.MethodCallFlowFunction +import org.seqra.dataflow.ap.ifds.analysis.MethodCallResolver import org.seqra.dataflow.ap.ifds.analysis.MethodCallSummaryHandler import org.seqra.dataflow.ap.ifds.analysis.MethodSequentFlowFunction import org.seqra.dataflow.ap.ifds.analysis.MethodSideEffectSummaryHandler @@ -46,7 +47,7 @@ import org.seqra.util.analysis.ApplicationGraph class JIRAnalysisManager( cp: JIRClasspath, - private val applyAliasInfo: Boolean = true, + private val aliasAnalysisParams: JIRLocalAliasAnalysis.Params = JIRLocalAliasAnalysis.Params(), ) : JIRLanguageManager(cp), TaintAnalysisManager { private val lambdaTracker = JIRLambdaTracker() override val factTypeChecker = JIRFactTypeChecker(cp) @@ -66,14 +67,16 @@ class JIRAnalysisManager( override fun getMethodAnalysisContext( methodEntryPoint: MethodEntryPoint, graph: ApplicationGraph, + callResolver: MethodCallResolver, taintAnalysisContext: TaintAnalysisContext ): MethodAnalysisContext { val entryPointStatement = methodEntryPoint.statement jIRDowncast(entryPointStatement) jIRDowncast(graph) + callResolver as JIRMethodCallResolver - val aliasAnalysis = if (applyAliasInfo) { - JIRLocalAliasAnalysis(entryPointStatement, graph, this) + val aliasAnalysis = if (aliasAnalysisParams.useAliasAnalysis) { + JIRLocalAliasAnalysis(entryPointStatement, graph, callResolver, this, aliasAnalysisParams) } else { null } diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt index 3831458..e258f1f 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt @@ -40,6 +40,7 @@ import org.seqra.dataflow.jvm.ap.ifds.taint.TaintCleanActionEvaluator import org.seqra.dataflow.jvm.ap.ifds.taint.TaintPassActionEvaluator import org.seqra.dataflow.jvm.ap.ifds.taint.TaintRulesProvider import org.seqra.dataflow.jvm.ap.ifds.taint.TaintSourceActionEvaluator +import org.seqra.dataflow.jvm.ap.ifds.taint.UserDefinedRuleInfo import org.seqra.dataflow.jvm.util.callee import org.seqra.dataflow.util.cartesianProductMapTo import org.seqra.ir.api.jvm.JIRMethod @@ -73,19 +74,19 @@ class JIRMethodCallFlowFunction( conditionRewriter, factReader = null, markAfterAnyFieldResolver = null ).forEach { (fact, trace) -> - fact.forEachFactWithAliases { this += CallToReturnZFact(factAp = it, trace) } + fact.forEachSourceFactWithAliases { this += CallToReturnZFact(factAp = it, trace) } } applySourceRules( initialFacts = emptySet(), conditionRewriter, factReader = null, exclusion = ExclusionSet.Universe, createFinalFact = { fact, trace -> - fact.forEachFactWithAliases { this += CallToReturnZFact(factAp = it, trace) } + fact.forEachSourceFactWithAliases { this += CallToReturnZFact(factAp = it, trace) } }, createEdge = { initial, final, trace -> - final.forEachFactWithAliases { this += CallToReturnFFact(initial, it, trace) } + final.forEachSourceFactWithAliases { this += CallToReturnFFact(initial, it, trace) } }, createNDEdge = { initial, final, trace -> - final.forEachFactWithAliases { this += CallToReturnNonDistributiveFact(initial, it, trace) } + final.forEachSourceFactWithAliases { this += CallToReturnNonDistributiveFact(initial, it, trace) } } ) @@ -209,7 +210,7 @@ class JIRMethodCallFlowFunction( } applySinkRules(conditionRewriter, factReader, markAfterAnyFieldResolver).forEach { (fact, trace) -> - fact.forEachFactWithAliases { + fact.forEachSourceFactWithAliases { addUnchecked(CallToReturnZFact(it, trace)) } } @@ -217,15 +218,15 @@ class JIRMethodCallFlowFunction( applySourceRules( initialFacts, conditionRewriter, factReader, exclusion, createFinalFact = { fact, trace -> - fact.forEachFactWithAliases { addCallToReturn(factReader, it, trace) } + fact.forEachSourceFactWithAliases { addCallToReturn(factReader, it, trace) } }, createEdge = { initial, final, trace -> - final.forEachFactWithAliases { + final.forEachSourceFactWithAliases { addUnchecked(CallToReturnFFact(initial, it, trace)) } }, createNDEdge = { initial, final, trace -> - final.forEachFactWithAliases { + final.forEachSourceFactWithAliases { addUnchecked(CallToReturnNonDistributiveFact(initial, it, trace)) } } @@ -286,7 +287,9 @@ class JIRMethodCallFlowFunction( for (cleanerResult in cleanerResults) { val factReaderAfterCleaner = cleanerResult.fact if (factReaderAfterCleaner == null) { - val trace = cleanerResult.action?.let { TraceInfo.Rule(it.rule, it.action) } + val trace = cleanerResult.action + ?.takeIf { it.rule.info is UserDefinedRuleInfo } + ?.let { TraceInfo.Rule(it.rule, it.action) } addCallToReturnUnchecked(MethodCallFlowFunction.Drop(trace)) continue } @@ -342,10 +345,8 @@ class JIRMethodCallFlowFunction( val trace = TraceInfo.Rule(evp.rule, evp.action) - addCallToReturn(factReaderAfterCleaner, mappedFact, trace) - - analysisContext.aliasAnalysis?.forEachAliasAtStatement(statement, mappedFact) { aliased -> - addCallToReturn(factReaderAfterCleaner, aliased, trace) + mappedFact.forEachFactWithAliases(originalFactReader.factAp) { + addCallToReturn(factReaderAfterCleaner, it, trace) } } } @@ -628,10 +629,17 @@ class JIRMethodCallFlowFunction( FinalFactReaderWithPrefix(it, ElementAccessor) } - private inline fun FinalFactAp.forEachFactWithAliases(crossinline body: (FinalFactAp) -> Unit) { + private inline fun FinalFactAp.forEachSourceFactWithAliases(crossinline body: (FinalFactAp) -> Unit) = + forEachFactWithAliases(originalFact = null, body) + + private inline fun FinalFactAp.forEachFactWithAliases(originalFact: FinalFactAp?, crossinline body: (FinalFactAp) -> Unit) { body(this) - analysisContext.aliasAnalysis?.forEachAliasAtStatement(statement, this) { aliased -> + if (originalFact != null && originalFact == this) { + return + } + + analysisContext.aliasAnalysis?.forEachAliasAfterCallStatement(statement, this) { aliased -> body(aliased) } } diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallResolver.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallResolver.kt index 0a9e938..55fa22a 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallResolver.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallResolver.kt @@ -20,7 +20,7 @@ import org.seqra.dataflow.jvm.ap.ifds.jIRDowncast class JIRMethodCallResolver( private val lambdaTracker: JIRLambdaTracker, - private val callResolver: JIRCallResolver, + val callResolver: JIRCallResolver, private val runner: TaintAnalysisUnitRunner ) : MethodCallResolver { override fun resolveMethodCall( diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallSummaryHandler.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallSummaryHandler.kt index f4a29e1..4ce9836 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallSummaryHandler.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallSummaryHandler.kt @@ -7,6 +7,7 @@ import org.seqra.dataflow.ap.ifds.MethodSummaryEdgeApplicationUtils import org.seqra.dataflow.ap.ifds.access.ApManager import org.seqra.dataflow.ap.ifds.access.FinalFactAp import org.seqra.dataflow.ap.ifds.analysis.MethodCallSummaryHandler +import org.seqra.dataflow.ap.ifds.analysis.MethodCallSummaryHandler.SummaryEdge import org.seqra.dataflow.ap.ifds.analysis.MethodSequentFlowFunction.Sequent import org.seqra.dataflow.jvm.ap.ifds.JIRMethodCallFactMapper import org.seqra.ir.api.jvm.cfg.JIRInst @@ -28,7 +29,7 @@ class JIRMethodCallSummaryHandler( override fun handleSummary( currentFactAp: FinalFactAp, summaryEffect: MethodSummaryEdgeApplicationUtils.SummaryEdgeApplication, - summaryFact: FinalFactAp, + summaryEdge: SummaryEdge, createSideEffectRequirement: (refinement: ExclusionSet) -> Sequent?, handleSummaryEdge: (initialFactRefinement: ExclusionSet?, summaryFactAp: FinalFactAp) -> Sequent ): Set { @@ -37,15 +38,17 @@ class JIRMethodCallSummaryHandler( result += super.handleSummary( currentFactAp, summaryEffect, - summaryFact, + summaryEdge, createSideEffectRequirement, ) { initialFactRefinement: ExclusionSet?, summaryFactAp: FinalFactAp -> if (initialFactRefinement != null) { createSideEffectRequirement(initialFactRefinement)?.also { result.add(it) } } - analysisContext.aliasAnalysis?.forEachAliasAfterStatement(statement, summaryFactAp) { aliased -> - result += handleSummaryEdge(initialFactRefinement, aliased) + if (summaryEdge.hasMemoryEffect()) { + analysisContext.aliasAnalysis?.forEachAliasAfterCallStatement(statement, summaryFactAp) { aliased -> + result += handleSummaryEdge(initialFactRefinement, aliased) + } } handleSummaryEdge(initialFactRefinement, summaryFactAp) @@ -74,4 +77,9 @@ class JIRMethodCallSummaryHandler( resultFact, ) } + + private fun SummaryEdge.hasMemoryEffect(): Boolean { + if (this !is SummaryEdge.F2F) return true + return !final.equalTo(initial) + } }