diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 72e8326566..cbd46db18e 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -2,7 +2,7 @@ plugins { `kotlin-dsl` } -val kotlinVersion = "1.9.20" +val kotlinVersion = "2.1.0" val detektVersion = "1.23.5" val gjavahVersion = "0.3.1" diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 5b388197b3..1f3b2f8c76 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -5,14 +5,14 @@ import org.gradle.plugin.use.PluginDependenciesSpec object Versions { const val detekt = "1.18.1" const val ini4j = "0.5.4" - const val jacodb = "81ccf5aaed" + const val jacodb = "6539d5020c" const val juliet = "1.3.2" const val junit = "5.9.3" - const val kotlin = "1.9.20" - const val kotlin_logging = "1.8.3" - const val kotlinx_collections = "0.3.5" - const val kotlinx_coroutines = "1.6.4" - const val kotlinx_serialization = "1.4.1" + const val kotlin = "2.1.0" + const val kotlin_logging = "3.0.5" + const val kotlinx_collections = "0.3.8" + const val kotlinx_coroutines = "1.10.0" + const val kotlinx_serialization = "1.7.3" const val ksmt = "0.5.26" const val logback = "1.4.8" const val mockk = "1.13.4" diff --git a/buildSrc/src/main/kotlin/usvm.kotlin-conventions.gradle.kts b/buildSrc/src/main/kotlin/usvm.kotlin-conventions.gradle.kts index 834b631322..731d716f2d 100644 --- a/buildSrc/src/main/kotlin/usvm.kotlin-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/usvm.kotlin-conventions.gradle.kts @@ -38,7 +38,6 @@ tasks { withType { kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() - freeCompilerArgs += "-Xallow-result-return-type" freeCompilerArgs += "-Xsam-conversions=class" allWarningsAsErrors = true } diff --git a/usvm-dataflow/build.gradle.kts b/usvm-dataflow/build.gradle.kts index 088a45b92d..a4724708fc 100644 --- a/usvm-dataflow/build.gradle.kts +++ b/usvm-dataflow/build.gradle.kts @@ -1,5 +1,3 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { id("usvm.kotlin-conventions") } @@ -10,12 +8,6 @@ dependencies { implementation(Libs.sarif4k) } -tasks.withType { - kotlinOptions { - freeCompilerArgs += "-Xcontext-receivers" - } -} - publishing { publications { create("maven") { diff --git a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/config/Condition.kt b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/config/Condition.kt index 0cd017325b..ee9c915b16 100644 --- a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/config/Condition.kt +++ b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/config/Condition.kt @@ -41,9 +41,9 @@ import org.usvm.dataflow.taint.Tainted import org.usvm.dataflow.util.Traits import org.usvm.dataflow.util.removeTrailingElementAccessors -context(Traits) open class BasicConditionEvaluator( - internal val positionResolver: PositionResolver>, + val traits: Traits, + internal val positionResolver: PositionResolver> ) : ConditionVisitor { override fun visit(condition: ConstantTrue): Boolean { @@ -74,35 +74,35 @@ open class BasicConditionEvaluator( error("Unexpected condition: $condition") } - override fun visit(condition: IsConstant): Boolean { + override fun visit(condition: IsConstant): Boolean = with(traits) { positionResolver.resolve(condition.position).onSome { return isConstant(it) } return false } - override fun visit(condition: ConstantEq): Boolean { + override fun visit(condition: ConstantEq): Boolean = with(traits) { positionResolver.resolve(condition.position).onSome { value -> return eqConstant(value, condition.value) } return false } - override fun visit(condition: ConstantLt): Boolean { + override fun visit(condition: ConstantLt): Boolean = with(traits) { positionResolver.resolve(condition.position).onSome { value -> return ltConstant(value, condition.value) } return false } - override fun visit(condition: ConstantGt): Boolean { + override fun visit(condition: ConstantGt): Boolean = with(traits) { positionResolver.resolve(condition.position).onSome { value -> return gtConstant(value, condition.value) } return false } - override fun visit(condition: ConstantMatches): Boolean { + override fun visit(condition: ConstantMatches): Boolean = with(traits) { positionResolver.resolve(condition.position).onSome { value -> return matches(value, condition.pattern) } @@ -117,7 +117,7 @@ open class BasicConditionEvaluator( error("This visitor does not support condition $condition. Use FactAwareConditionEvaluator instead") } - override fun visit(condition: TypeMatches): Boolean { + override fun visit(condition: TypeMatches): Boolean = with(traits) { positionResolver.resolve(condition.position).onSome { value -> return typeMatches(value, condition) } @@ -125,13 +125,13 @@ open class BasicConditionEvaluator( } } -context(Traits) class FactAwareConditionEvaluator( + traits: Traits, private val fact: Tainted, positionResolver: PositionResolver>, -) : BasicConditionEvaluator(positionResolver) { +) : BasicConditionEvaluator(traits, positionResolver) { - override fun visit(condition: ContainsMark): Boolean { + override fun visit(condition: ContainsMark): Boolean = with(traits) { if (fact.mark != condition.mark) return false positionResolver.resolve(condition.position).onSome { value -> val variable = convertToPath(value) diff --git a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/config/Position.kt b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/config/Position.kt index e85cb78cf9..e51204caba 100644 --- a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/config/Position.kt +++ b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/config/Position.kt @@ -35,28 +35,30 @@ import org.usvm.dataflow.ifds.fmap import org.usvm.dataflow.ifds.toMaybe import org.usvm.dataflow.util.Traits -context(Traits) class CallPositionToAccessPathResolver( + private val traits: Traits, private val callStatement: CommonInst, ) : PositionResolver> { - private val callExpr = getCallExpr(callStatement) + private val callExpr = traits.getCallExpr(callStatement) ?: error("Call statement should have non-null callExpr") - override fun resolve(position: Position): Maybe = when (position) { - AnyArgument -> Maybe.none() - is Argument -> convertToPathOrNull(callExpr.args[position.index]).toMaybe() - This -> (callExpr as? CommonInstanceCallExpr)?.instance?.let { convertToPathOrNull(it) }.toMaybe() - Result -> (callStatement as? CommonAssignInst)?.lhv?.let { convertToPathOrNull(it) }.toMaybe() - ResultAnyElement -> (callStatement as? CommonAssignInst)?.lhv?.let { convertToPathOrNull(it) }.toMaybe() - .fmap { it + ElementAccessor } + override fun resolve(position: Position): Maybe = with(traits) { + when (position) { + AnyArgument -> Maybe.none() + is Argument -> convertToPathOrNull(callExpr.args[position.index]).toMaybe() + This -> (callExpr as? CommonInstanceCallExpr)?.instance?.let { convertToPathOrNull(it) }.toMaybe() + Result -> (callStatement as? CommonAssignInst)?.lhv?.let { convertToPathOrNull(it) }.toMaybe() + ResultAnyElement -> (callStatement as? CommonAssignInst)?.lhv?.let { convertToPathOrNull(it) }.toMaybe() + .fmap { it + ElementAccessor } + } } } -context(Traits) class CallPositionToValueResolver( + traits: Traits, private val callStatement: CommonInst, ) : PositionResolver> { - private val callExpr = getCallExpr(callStatement) + private val callExpr = traits.getCallExpr(callStatement) ?: error("Call statement should have non-null callExpr") override fun resolve(position: Position): Maybe = when (position) { @@ -68,34 +70,38 @@ class CallPositionToValueResolver( } } -context(Traits) class EntryPointPositionToValueResolver( + private val traits: Traits, private val method: CommonMethod, ) : PositionResolver> { - override fun resolve(position: Position): Maybe = when (position) { - This -> Maybe.some(getThisInstance(method)) + override fun resolve(position: Position): Maybe = with(traits) { + when (position) { + This -> Maybe.some(getThisInstance(method)) - is Argument -> { - val p = method.parameters[position.index] - getArgument(p).toMaybe() - } + is Argument -> { + val p = method.parameters[position.index] + getArgument(p).toMaybe() + } - AnyArgument, Result, ResultAnyElement -> error("Unexpected $position") + AnyArgument, Result, ResultAnyElement -> error("Unexpected $position") + } } } -context(Traits) class EntryPointPositionToAccessPathResolver( + private val traits: Traits, private val method: CommonMethod, ) : PositionResolver> { - override fun resolve(position: Position): Maybe = when (position) { - This -> convertToPathOrNull(getThisInstance(method)).toMaybe() + override fun resolve(position: Position): Maybe = with(traits) { + when (position) { + This -> convertToPathOrNull(getThisInstance(method)).toMaybe() - is Argument -> { - val p = method.parameters[position.index] - getArgument(p)?.let { convertToPathOrNull(it) }.toMaybe() - } + is Argument -> { + val p = method.parameters[position.index] + getArgument(p)?.let { convertToPathOrNull(it) }.toMaybe() + } - AnyArgument, Result, ResultAnyElement -> error("Unexpected $position") + AnyArgument, Result, ResultAnyElement -> error("Unexpected $position") + } } } diff --git a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/ifds/Runner.kt b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/ifds/Runner.kt index c5281712eb..e5552622cb 100644 --- a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/ifds/Runner.kt +++ b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/ifds/Runner.kt @@ -41,8 +41,8 @@ interface Runner fun getIfdsResult(): IfdsResult } -context(Traits) class UniRunner( + private val traits: Traits, private val manager: Manager, override val graph: ApplicationGraph, private val analyzer: Analyzer, @@ -139,7 +139,7 @@ class UniRunner( private fun tabulationAlgorithmStep( currentEdge: Edge, scope: CoroutineScope, - ) { + ) = with(traits) { val (startVertex, currentVertex) = currentEdge val (current, currentFact) = currentVertex diff --git a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/sarif/Sarif.kt b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/sarif/Sarif.kt index f2b6727ac0..096f3993cf 100644 --- a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/sarif/Sarif.kt +++ b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/sarif/Sarif.kt @@ -42,8 +42,8 @@ private const val JACODB_INFORMATION_URI = "https://github.com/UnitTestBot/jacodb/blob/develop/jacodb-analysis/README.md" private const val DEFAULT_PATH_COUNT = 3 -context(Traits<*, Statement>) fun sarifReportFromVulnerabilities( + traits: Traits<*, Statement>, vulnerabilities: List>, maxPathsCount: Int = DEFAULT_PATH_COUNT, isDeduplicate: Boolean = true, @@ -71,6 +71,7 @@ fun sarifReportFromVulnerabilities( level = instance.description.level, locations = listOfNotNull( instToSarifLocation( + traits, instance.traceGraph.sink.statement, sourceFileResolver ) @@ -78,7 +79,7 @@ fun sarifReportFromVulnerabilities( codeFlows = instance.traceGraph .getAllTraces() .take(maxPathsCount) - .map { traceToSarifCodeFlow(it, sourceFileResolver, isDeduplicate) } + .map { traceToSarifCodeFlow(traits, it, sourceFileResolver, isDeduplicate) } .toList(), ) } @@ -87,11 +88,11 @@ fun sarifReportFromVulnerabilities( ) } -context(Traits<*, Statement>) private fun instToSarifLocation( + traits: Traits<*, Statement>, inst: Statement, sourceFileResolver: SourceFileResolver, -): Location? { +): Location? = with(traits) { val sourceLocation = sourceFileResolver.resolve(inst) ?: return null return Location( physicalLocation = PhysicalLocation( @@ -110,8 +111,8 @@ private fun instToSarifLocation( ) } -context(Traits<*, Statement>) private fun traceToSarifCodeFlow( + traits: Traits<*, Statement>, trace: List>, sourceFileResolver: SourceFileResolver, isDeduplicate: Boolean = true, @@ -121,7 +122,7 @@ private fun traceToSarifCodeFlow( ThreadFlow( locations = trace.map { ThreadFlowLocation( - location = instToSarifLocation(it.statement, sourceFileResolver), + location = instToSarifLocation(traits, it.statement, sourceFileResolver), state = mapOf( "fact" to MultiformatMessageString( text = it.fact.toString() diff --git a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/taint/TaintAnalyzers.kt b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/taint/TaintAnalyzers.kt index 30cf52fefe..8899bb4a34 100644 --- a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/taint/TaintAnalyzers.kt +++ b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/taint/TaintAnalyzers.kt @@ -31,16 +31,16 @@ import org.usvm.dataflow.util.Traits private val logger = object : KLogging() {}.logger -context(Traits) class TaintAnalyzer( + private val traits: Traits, private val graph: ApplicationGraph, - private val getConfigForMethod: (Method) -> List?, + private val getConfigForMethod: (Method) -> List? ) : Analyzer, Method, Statement> where Method : CommonMethod, Statement : CommonInst { override val flowFunctions: ForwardTaintFlowFunctions by lazy { - ForwardTaintFlowFunctions(graph, getConfigForMethod) + ForwardTaintFlowFunctions(traits, graph, getConfigForMethod) } private fun isExitPoint(statement: Statement): Boolean { @@ -49,85 +49,88 @@ class TaintAnalyzer( override fun handleNewEdge( edge: TaintEdge, - ): List> = buildList { - if (isExitPoint(edge.to.statement)) { - add(NewSummaryEdge(edge)) - } + ): List> = with(traits) { + buildList { + if (isExitPoint(edge.to.statement)) { + add(NewSummaryEdge(edge)) + } - run { - val callExpr = getCallExpr(edge.to.statement) ?: return@run + run { + val callExpr = getCallExpr(edge.to.statement) ?: return@run - val callee = getCallee(callExpr) + val callee = getCallee(callExpr) - val config = getConfigForMethod(callee) ?: return@run + val config = getConfigForMethod(callee) ?: return@run - // TODO: not always we want to skip sinks on Zero facts. - // Some rules might have ConstantTrue or just true (when evaluated with Zero fact) condition. - if (edge.to.fact !is Tainted) { - return@run - } + // TODO: not always we want to skip sinks on Zero facts. + // Some rules might have ConstantTrue or just true (when evaluated with Zero fact) condition. + if (edge.to.fact !is Tainted) { + return@run + } - // Determine whether 'edge.to' is a sink via config: - val conditionEvaluator = FactAwareConditionEvaluator( - edge.to.fact, - CallPositionToValueResolver(edge.to.statement), - ) - for (item in config.filterIsInstance()) { - if (item.condition.accept(conditionEvaluator)) { - val message = item.ruleNote - val vulnerability = TaintVulnerability(message, sink = edge.to, rule = item) - logger.info { - "Found sink=${vulnerability.sink} in ${vulnerability.method} on $item" + // Determine whether 'edge.to' is a sink via config: + val conditionEvaluator = FactAwareConditionEvaluator( + traits, + edge.to.fact, + CallPositionToValueResolver(traits, edge.to.statement), + ) + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + val message = item.ruleNote + val vulnerability = TaintVulnerability(message, sink = edge.to, rule = item) + logger.info { + "Found sink=${vulnerability.sink} in ${vulnerability.method} on $item" + } + add(NewVulnerability(vulnerability)) } - add(NewVulnerability(vulnerability)) } } - } - if (TaintAnalysisOptions.UNTRUSTED_LOOP_BOUND_SINK) { - val statement = edge.to.statement - val fact = edge.to.fact - if (fact is Tainted && fact.mark.name == "UNTRUSTED") { - val branchExprCondition = getBranchExprCondition(statement) - if (branchExprCondition != null && isLoopHead(statement)) { - val conditionOperands = getValues(branchExprCondition) - for (s in conditionOperands) { - val p = convertToPath(s) - if (p == fact.variable) { - val message = "Untrusted loop bound" - val vulnerability = TaintVulnerability(message, sink = edge.to) - add(NewVulnerability(vulnerability)) + if (TaintAnalysisOptions.UNTRUSTED_LOOP_BOUND_SINK) { + val statement = edge.to.statement + val fact = edge.to.fact + if (fact is Tainted && fact.mark.name == "UNTRUSTED") { + val branchExprCondition = getBranchExprCondition(statement) + if (branchExprCondition != null && isLoopHead(statement)) { + val conditionOperands = getValues(branchExprCondition) + for (s in conditionOperands) { + val p = convertToPath(s) + if (p == fact.variable) { + val message = "Untrusted loop bound" + val vulnerability = TaintVulnerability(message, sink = edge.to) + add(NewVulnerability(vulnerability)) + } } } } } - } - if (TaintAnalysisOptions.UNTRUSTED_ARRAY_SIZE_SINK) { - val statement = edge.to.statement - val fact = edge.to.fact - if (fact is Tainted && fact.mark.name == "UNTRUSTED") { - val arrayAllocation = getArrayAllocation(statement) - if (arrayAllocation != null) { - for (arg in getValues(arrayAllocation)) { - if (convertToPath(arg) == fact.variable) { - val message = "Untrusted array size" - val vulnerability = TaintVulnerability(message, sink = edge.to) - add(NewVulnerability(vulnerability)) + if (TaintAnalysisOptions.UNTRUSTED_ARRAY_SIZE_SINK) { + val statement = edge.to.statement + val fact = edge.to.fact + if (fact is Tainted && fact.mark.name == "UNTRUSTED") { + val arrayAllocation = getArrayAllocation(statement) + if (arrayAllocation != null) { + for (arg in getValues(arrayAllocation)) { + if (convertToPath(arg) == fact.variable) { + val message = "Untrusted array size" + val vulnerability = TaintVulnerability(message, sink = edge.to) + add(NewVulnerability(vulnerability)) + } } } } } - } - if (TaintAnalysisOptions.UNTRUSTED_INDEX_ARRAY_ACCESS_SINK) { - val statement = edge.to.statement - val fact = edge.to.fact - if (fact is Tainted && fact.mark.name == "UNTRUSTED") { - val arrayAccessIndex = getArrayAccessIndex(statement) - if (arrayAccessIndex != null) { - if (convertToPath(arrayAccessIndex) == fact.variable) { - val message = "Untrusted index for access array" - val vulnerability = TaintVulnerability(message, sink = edge.to) - add(NewVulnerability(vulnerability)) + if (TaintAnalysisOptions.UNTRUSTED_INDEX_ARRAY_ACCESS_SINK) { + val statement = edge.to.statement + val fact = edge.to.fact + if (fact is Tainted && fact.mark.name == "UNTRUSTED") { + val arrayAccessIndex = getArrayAccessIndex(statement) + if (arrayAccessIndex != null) { + if (convertToPath(arrayAccessIndex) == fact.variable) { + val message = "Untrusted index for access array" + val vulnerability = TaintVulnerability(message, sink = edge.to) + add(NewVulnerability(vulnerability)) + } } } } @@ -142,15 +145,15 @@ class TaintAnalyzer( } } -context(Traits) class BackwardTaintAnalyzer( + private val traits: Traits, private val graph: ApplicationGraph, ) : Analyzer, Method, Statement> where Method : CommonMethod, Statement : CommonInst { override val flowFunctions: BackwardTaintFlowFunctions by lazy { - BackwardTaintFlowFunctions(graph) + BackwardTaintFlowFunctions(traits, graph) } private fun isExitPoint(statement: Statement): Boolean { diff --git a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/taint/TaintFlowFunctions.kt b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/taint/TaintFlowFunctions.kt index 657b1c0a45..775cad9440 100644 --- a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/taint/TaintFlowFunctions.kt +++ b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/taint/TaintFlowFunctions.kt @@ -54,8 +54,8 @@ import org.usvm.dataflow.util.startsWith private val logger = mu.KotlinLogging.logger {} -context(Traits) class ForwardTaintFlowFunctions( + private val traits: Traits, private val graph: ApplicationGraph, val getConfigForMethod: (Method) -> List?, ) : FlowFunctions @@ -72,10 +72,11 @@ class ForwardTaintFlowFunctions( val config = getConfigForMethod(method) if (config != null) { val conditionEvaluator = BasicConditionEvaluator( - EntryPointPositionToValueResolver(method) + traits, + EntryPointPositionToValueResolver(traits, method) ) val actionEvaluator = TaintActionEvaluator( - EntryPointPositionToAccessPathResolver(method) + EntryPointPositionToAccessPathResolver(traits, method) ) // Handle EntryPointSource config items: @@ -97,7 +98,7 @@ class ForwardTaintFlowFunctions( fact: Tainted, from: CommonExpr, to: CommonValue, - ): Collection { + ): Collection = with(traits) { val toPath = convertToPath(to) val fromPath = convertToPathOrNull(from) @@ -139,18 +140,20 @@ class ForwardTaintFlowFunctions( override fun obtainSequentFlowFunction( current: Statement, next: Statement, - ) = FlowFunction { fact -> - if (fact is TaintZeroFact) { - return@FlowFunction listOf(TaintZeroFact) - } - check(fact is Tainted) + ) = with(traits) { + FlowFunction { fact -> + if (fact is TaintZeroFact) { + return@FlowFunction listOf(TaintZeroFact) + } + check(fact is Tainted) - if (current is CommonAssignInst) { - taintFlowRhsValues(current).flatMap { rhv -> - transmitTaintAssign(fact, from = rhv, to = current.lhv) + if (current is CommonAssignInst) { + taintFlowRhsValues(current).flatMap { rhv -> + transmitTaintAssign(fact, from = rhv, to = current.lhv) + } + } else { + transmitTaintNormal(fact) } - } else { - transmitTaintNormal(fact) } } @@ -158,14 +161,16 @@ class ForwardTaintFlowFunctions( fact: Tainted, from: CommonValue, to: CommonValue, - ): Collection = buildSet { - val fromPath = convertToPath(from) - val toPath = convertToPath(to) + ): Collection = with(traits) { + buildSet { + val fromPath = convertToPath(from) + val toPath = convertToPath(to) - val tail = (fact.variable - fromPath) ?: return@buildSet - val newPath = toPath + tail - val newTaint = fact.copy(variable = newPath) - add(newTaint) + val tail = (fact.variable - fromPath) ?: return@buildSet + val newPath = toPath + tail + val newTaint = fact.copy(variable = newPath) + add(newTaint) + } } private fun transmitTaintArgumentActualToFormal( @@ -201,195 +206,200 @@ class ForwardTaintFlowFunctions( override fun obtainCallToReturnSiteFlowFunction( callStatement: Statement, returnSite: Statement, // FIXME: unused? - ) = FlowFunction { fact -> - val callExpr = getCallExpr(callStatement) - ?: error("Call statement should have non-null callExpr") - - val callee = getCallee(callExpr) - val config = getConfigForMethod(callee) - - if (fact == TaintZeroFact) { - return@FlowFunction buildSet { - add(TaintZeroFact) - - if (config != null) { - val conditionEvaluator = BasicConditionEvaluator( - CallPositionToValueResolver(callStatement) - ) - val actionEvaluator = TaintActionEvaluator( - CallPositionToAccessPathResolver(callStatement) - ) + ) = with(traits) { + FlowFunction { fact -> + val callExpr = getCallExpr(callStatement) + ?: error("Call statement should have non-null callExpr") + + val callee = getCallee(callExpr) + val config = getConfigForMethod(callee) + + if (fact == TaintZeroFact) { + return@FlowFunction buildSet { + add(TaintZeroFact) + + if (config != null) { + val conditionEvaluator = BasicConditionEvaluator( + traits, + CallPositionToValueResolver(traits, callStatement) + ) + val actionEvaluator = TaintActionEvaluator( + CallPositionToAccessPathResolver(traits, callStatement) + ) - // Handle MethodSource config items: - for (item in config.filterIsInstance()) { - if (item.condition.accept(conditionEvaluator)) { - for (action in item.actionsAfter) { - val result = when (action) { - is AssignMark -> actionEvaluator.evaluate(action) - else -> error("$action is not supported for $item") + // Handle MethodSource config items: + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + for (action in item.actionsAfter) { + val result = when (action) { + is AssignMark -> actionEvaluator.evaluate(action) + else -> error("$action is not supported for $item") + } + result.onSome { addAll(it) } } - result.onSome { addAll(it) } } } } } } - } - check(fact is Tainted) + check(fact is Tainted) - val statementPassThrough = taintPassThrough(callStatement) - if (statementPassThrough != null) { - for ((from, to) in statementPassThrough) { - if (convertToPath(from) == fact.variable) { - return@FlowFunction setOf( - fact, - fact.copy(variable = convertToPath(to)) - ) + val statementPassThrough = taintPassThrough(callStatement) + if (statementPassThrough != null) { + for ((from, to) in statementPassThrough) { + if (convertToPath(from) == fact.variable) { + return@FlowFunction setOf( + fact, + fact.copy(variable = convertToPath(to)) + ) + } } - } - - return@FlowFunction setOf(fact) - } - if (config != null) { - val facts = mutableSetOf() - val conditionEvaluator = FactAwareConditionEvaluator( - fact, CallPositionToValueResolver(callStatement) - ) - val actionEvaluator = TaintActionEvaluator( - CallPositionToAccessPathResolver(callStatement) - ) - var defaultBehavior = true + return@FlowFunction setOf(fact) + } - // Handle PassThrough config items: - for (item in config.filterIsInstance()) { - if (item.condition.accept(conditionEvaluator)) { - for (action in item.actionsAfter) { - val result = when (action) { - is CopyMark -> actionEvaluator.evaluate(action, fact) - is CopyAllMarks -> actionEvaluator.evaluate(action, fact) - is RemoveMark -> actionEvaluator.evaluate(action, fact) - is RemoveAllMarks -> actionEvaluator.evaluate(action, fact) - else -> error("$action is not supported for $item") - } - result.onSome { - facts += it - defaultBehavior = false + if (config != null) { + val facts = mutableSetOf() + val conditionEvaluator = FactAwareConditionEvaluator( + traits, fact, CallPositionToValueResolver(traits, callStatement) + ) + val actionEvaluator = TaintActionEvaluator( + CallPositionToAccessPathResolver(traits, callStatement) + ) + var defaultBehavior = true + + // Handle PassThrough config items: + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + for (action in item.actionsAfter) { + val result = when (action) { + is CopyMark -> actionEvaluator.evaluate(action, fact) + is CopyAllMarks -> actionEvaluator.evaluate(action, fact) + is RemoveMark -> actionEvaluator.evaluate(action, fact) + is RemoveAllMarks -> actionEvaluator.evaluate(action, fact) + else -> error("$action is not supported for $item") + } + result.onSome { + facts += it + defaultBehavior = false + } } } } - } - // Handle Cleaner config items: - for (item in config.filterIsInstance()) { - if (item.condition.accept(conditionEvaluator)) { - for (action in item.actionsAfter) { - val result = when (action) { - is RemoveMark -> actionEvaluator.evaluate(action, fact) - is RemoveAllMarks -> actionEvaluator.evaluate(action, fact) - else -> error("$action is not supported for $item") - } - result.onSome { - facts += it - defaultBehavior = false + // Handle Cleaner config items: + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + for (action in item.actionsAfter) { + val result = when (action) { + is RemoveMark -> actionEvaluator.evaluate(action, fact) + is RemoveAllMarks -> actionEvaluator.evaluate(action, fact) + else -> error("$action is not supported for $item") + } + result.onSome { + facts += it + defaultBehavior = false + } } } } - } - if (!defaultBehavior) { - if (facts.size > 0) { - logger.trace { "Got ${facts.size} facts from config for $callee: $facts" } + if (!defaultBehavior) { + if (facts.size > 0) { + logger.trace { "Got ${facts.size} facts from config for $callee: $facts" } + } + return@FlowFunction facts + } else { + // Fall back to the default behavior, as if there were no config at all. } - return@FlowFunction facts - } else { - // Fall back to the default behavior, as if there were no config at all. } - } - - // FIXME: adhoc for constructors: - if (isConstructor(callee)) { - return@FlowFunction listOf(fact) - } - // TODO: CONSIDER REFACTORING THIS - // Default behavior for "analyzable" method calls is to remove ("temporarily") - // all the marks from the 'instance' and arguments, in order to allow them "pass through" - // the callee (when it is going to be analyzed), i.e. through "call-to-start" and - // "exit-to-return" flow functions. - // When we know that we are NOT going to analyze the callee, we do NOT need - // to remove any marks from 'instance' and arguments. - // Currently, "analyzability" of the callee depends on the fact that the callee - // is "accessible" through the JcApplicationGraph::callees(). - if (callee in graph.callees(callStatement)) { - - if (fact.variable.isStatic) { - return@FlowFunction emptyList() + // FIXME: adhoc for constructors: + if (isConstructor(callee)) { + return@FlowFunction listOf(fact) } - for (actual in callExpr.args) { - // Possibly tainted actual parameter: - if (fact.variable.startsWith(convertToPathOrNull(actual))) { - return@FlowFunction emptyList() // Will be handled by summary edge + // TODO: CONSIDER REFACTORING THIS + // Default behavior for "analyzable" method calls is to remove ("temporarily") + // all the marks from the 'instance' and arguments, in order to allow them "pass through" + // the callee (when it is going to be analyzed), i.e. through "call-to-start" and + // "exit-to-return" flow functions. + // When we know that we are NOT going to analyze the callee, we do NOT need + // to remove any marks from 'instance' and arguments. + // Currently, "analyzability" of the callee depends on the fact that the callee + // is "accessible" through the JcApplicationGraph::callees(). + if (callee in graph.callees(callStatement)) { + + if (fact.variable.isStatic) { + return@FlowFunction emptyList() } - } - if (callExpr is CommonInstanceCallExpr) { - // Possibly tainted instance: - if (fact.variable.startsWith(convertToPathOrNull(callExpr.instance))) { - return@FlowFunction emptyList() // Will be handled by summary edge + for (actual in callExpr.args) { + // Possibly tainted actual parameter: + if (fact.variable.startsWith(convertToPathOrNull(actual))) { + return@FlowFunction emptyList() // Will be handled by summary edge + } } - } - } + if (callExpr is CommonInstanceCallExpr) { + // Possibly tainted instance: + if (fact.variable.startsWith(convertToPathOrNull(callExpr.instance))) { + return@FlowFunction emptyList() // Will be handled by summary edge + } + } - if (callStatement is CommonAssignInst) { - // Possibly tainted lhv: - if (fact.variable.startsWith(convertToPathOrNull(callStatement.lhv))) { - return@FlowFunction emptyList() // Overridden by rhv } - } - // The "most default" behaviour is encapsulated here: - transmitTaintNormal(fact) + if (callStatement is CommonAssignInst) { + // Possibly tainted lhv: + if (fact.variable.startsWith(convertToPathOrNull(callStatement.lhv))) { + return@FlowFunction emptyList() // Overridden by rhv + } + } + + // The "most default" behaviour is encapsulated here: + transmitTaintNormal(fact) + } } override fun obtainCallToStartFlowFunction( callStatement: Statement, calleeStart: Statement, - ) = FlowFunction { fact -> - val callee = graph.methodOf(calleeStart) + ) = with(traits) { + FlowFunction { fact -> + val callee = graph.methodOf(calleeStart) - if (fact == TaintZeroFact) { - return@FlowFunction obtainPossibleStartFacts(callee) - } - check(fact is Tainted) + if (fact == TaintZeroFact) { + return@FlowFunction obtainPossibleStartFacts(callee) + } + check(fact is Tainted) - val callExpr = getCallExpr(callStatement) - ?: error("Call statement should have non-null callExpr") + val callExpr = getCallExpr(callStatement) + ?: error("Call statement should have non-null callExpr") - buildSet { - // Transmit facts on arguments (from 'actual' to 'formal'): - val actualParams = callExpr.args - val formalParams = getArgumentsOf(callee) - for ((formal, actual) in formalParams.zip(actualParams)) { - addAll(transmitTaintArgumentActualToFormal(fact, from = actual, to = formal)) - } + buildSet { + // Transmit facts on arguments (from 'actual' to 'formal'): + val actualParams = callExpr.args + val formalParams = getArgumentsOf(callee) + for ((formal, actual) in formalParams.zip(actualParams)) { + addAll(transmitTaintArgumentActualToFormal(fact, from = actual, to = formal)) + } - // Transmit facts on instance (from 'instance' to 'this'): - if (callExpr is CommonInstanceCallExpr) { - addAll( - transmitTaintInstanceToThis( - fact = fact, - from = callExpr.instance, - to = getThisInstance(callee), + // Transmit facts on instance (from 'instance' to 'this'): + if (callExpr is CommonInstanceCallExpr) { + addAll( + transmitTaintInstanceToThis( + fact = fact, + from = callExpr.instance, + to = getThisInstance(callee), + ) ) - ) - } + } - // Transmit facts on static values: - if (fact.variable.isStatic) { - add(fact) + // Transmit facts on static values: + if (fact.variable.isStatic) { + add(fact) + } } } } @@ -398,61 +408,63 @@ class ForwardTaintFlowFunctions( callStatement: Statement, returnSite: Statement, // unused exitStatement: Statement, - ) = FlowFunction { fact -> - if (fact == TaintZeroFact) { - return@FlowFunction listOf(TaintZeroFact) - } - check(fact is Tainted) - - val callExpr = getCallExpr(callStatement) - ?: error("Call statement should have non-null callExpr") - val callee = graph.methodOf(exitStatement) + ) = with(traits) { + FlowFunction { fact -> + if (fact == TaintZeroFact) { + return@FlowFunction listOf(TaintZeroFact) + } + check(fact is Tainted) + + val callExpr = getCallExpr(callStatement) + ?: error("Call statement should have non-null callExpr") + val callee = graph.methodOf(exitStatement) + + buildSet { + // Transmit facts on arguments (from 'formal' back to 'actual'), if they are passed by-ref: + if (fact.variable.isOnHeap) { + val actualParams = callExpr.args + val formalParams = getArgumentsOf(callee) + for ((formal, actual) in formalParams.zip(actualParams)) { + addAll( + transmitTaintArgumentFormalToActual( + fact = fact, + from = formal, + to = actual, + ) + ) + } + } - buildSet { - // Transmit facts on arguments (from 'formal' back to 'actual'), if they are passed by-ref: - if (fact.variable.isOnHeap) { - val actualParams = callExpr.args - val formalParams = getArgumentsOf(callee) - for ((formal, actual) in formalParams.zip(actualParams)) { + // Transmit facts on instance (from 'this' to 'instance'): + if (callExpr is CommonInstanceCallExpr) { addAll( - transmitTaintArgumentFormalToActual( + transmitTaintThisToInstance( fact = fact, - from = formal, - to = actual, + from = getThisInstance(callee), + to = callExpr.instance, ) ) } - } - - // Transmit facts on instance (from 'this' to 'instance'): - if (callExpr is CommonInstanceCallExpr) { - addAll( - transmitTaintThisToInstance( - fact = fact, - from = getThisInstance(callee), - to = callExpr.instance, - ) - ) - } - // Transmit facts on static values: - if (fact.variable.isStatic) { - add(fact) - } + // Transmit facts on static values: + if (fact.variable.isStatic) { + add(fact) + } - // Transmit facts on return value (from 'returnValue' to 'lhv'): - if (exitStatement is CommonReturnInst && callStatement is CommonAssignInst) { - // Note: returnValue can be null here in some weird cases, e.g. in lambda. - exitStatement.returnValue?.let { returnValue -> - addAll(transmitTaintReturn(fact, from = returnValue, to = callStatement.lhv)) + // Transmit facts on return value (from 'returnValue' to 'lhv'): + if (exitStatement is CommonReturnInst && callStatement is CommonAssignInst) { + // Note: returnValue can be null here in some weird cases, e.g. in lambda. + exitStatement.returnValue?.let { returnValue -> + addAll(transmitTaintReturn(fact, from = returnValue, to = callStatement.lhv)) + } } } } } } -context(Traits) class BackwardTaintFlowFunctions( + private val traits: Traits, private val graph: ApplicationGraph, ) : FlowFunctions where Method : CommonMethod, @@ -468,7 +480,7 @@ class BackwardTaintFlowFunctions( fact: Tainted, from: CommonValue, to: CommonExpr, - ): Collection { + ): Collection = with(traits) { val fromPath = convertToPath(from) val toPath = convertToPathOrNull(to) @@ -518,14 +530,16 @@ class BackwardTaintFlowFunctions( fact: Tainted, from: CommonValue, to: CommonValue, - ): Collection = buildSet { - val fromPath = convertToPath(from) - val toPath = convertToPath(to) + ): Collection = with(traits) { + buildSet { + val fromPath = convertToPath(from) + val toPath = convertToPath(to) - val tail = (fact.variable - fromPath) ?: return@buildSet - val newPath = toPath + tail - val newTaint = fact.copy(variable = newPath) - add(newTaint) + val tail = (fact.variable - fromPath) ?: return@buildSet + val newPath = toPath + tail + val newTaint = fact.copy(variable = newPath) + add(newTaint) + } } private fun transmitTaintArgumentActualToFormal( @@ -561,101 +575,105 @@ class BackwardTaintFlowFunctions( override fun obtainCallToReturnSiteFlowFunction( callStatement: Statement, returnSite: Statement, // FIXME: unused? - ) = FlowFunction { fact -> - // TODO: pass-through on invokedynamic-based String concatenation + ) = with(traits) { + FlowFunction { fact -> + // TODO: pass-through on invokedynamic-based String concatenation - if (fact == TaintZeroFact) { - return@FlowFunction listOf(TaintZeroFact) - } - check(fact is Tainted) + if (fact == TaintZeroFact) { + return@FlowFunction listOf(TaintZeroFact) + } + check(fact is Tainted) - val callExpr = getCallExpr(callStatement) - ?: error("Call statement should have non-null callExpr") - val callee = getCallee(callExpr) + val callExpr = getCallExpr(callStatement) + ?: error("Call statement should have non-null callExpr") + val callee = getCallee(callExpr) - if (callee in graph.callees(callStatement)) { + if (callee in graph.callees(callStatement)) { - if (fact.variable.isStatic) { - return@FlowFunction emptyList() - } + if (fact.variable.isStatic) { + return@FlowFunction emptyList() + } - for (actual in callExpr.args) { - // Possibly tainted actual parameter: - if (fact.variable.startsWith(convertToPathOrNull(actual))) { - return@FlowFunction emptyList() // Will be handled by summary edge + for (actual in callExpr.args) { + // Possibly tainted actual parameter: + if (fact.variable.startsWith(convertToPathOrNull(actual))) { + return@FlowFunction emptyList() // Will be handled by summary edge + } } - } - if (callExpr is CommonInstanceCallExpr) { - // Possibly tainted instance: - if (fact.variable.startsWith(convertToPathOrNull(callExpr.instance))) { - return@FlowFunction emptyList() // Will be handled by summary edge + if (callExpr is CommonInstanceCallExpr) { + // Possibly tainted instance: + if (fact.variable.startsWith(convertToPathOrNull(callExpr.instance))) { + return@FlowFunction emptyList() // Will be handled by summary edge + } } - } - } + } - if (callStatement is CommonAssignInst) { - // Possibly tainted rhv: - if (fact.variable.startsWith(convertToPathOrNull(callStatement.rhv))) { - return@FlowFunction emptyList() // Overridden by lhv + if (callStatement is CommonAssignInst) { + // Possibly tainted rhv: + if (fact.variable.startsWith(convertToPathOrNull(callStatement.rhv))) { + return@FlowFunction emptyList() // Overridden by lhv + } } - } - // The "most default" behaviour is encapsulated here: - transmitTaintBackwardNormal(fact) + // The "most default" behaviour is encapsulated here: + transmitTaintBackwardNormal(fact) + } } override fun obtainCallToStartFlowFunction( callStatement: Statement, calleeStart: Statement, - ) = FlowFunction { fact -> - val callee = graph.methodOf(calleeStart) + ) = with(traits) { + FlowFunction { fact -> + val callee = graph.methodOf(calleeStart) - if (fact == TaintZeroFact) { - return@FlowFunction obtainPossibleStartFacts(callee) - } - check(fact is Tainted) - - val callExpr = getCallExpr(callStatement) - ?: error("Call statement should have non-null callExpr") - - buildSet { - // Transmit facts on arguments (from 'actual' to 'formal'): - val actualParams = callExpr.args - val formalParams = getArgumentsOf(callee) - for ((formal, actual) in formalParams.zip(actualParams)) { - addAll(transmitTaintArgumentActualToFormal(fact, from = actual, to = formal)) + if (fact == TaintZeroFact) { + return@FlowFunction obtainPossibleStartFacts(callee) } + check(fact is Tainted) - // Transmit facts on instance (from 'instance' to 'this'): - if (callExpr is CommonInstanceCallExpr) { - addAll( - transmitTaintInstanceToThis( - fact = fact, - from = callExpr.instance, - to = getThisInstance(callee), - ) - ) - } + val callExpr = getCallExpr(callStatement) + ?: error("Call statement should have non-null callExpr") - // Transmit facts on static values: - if (fact.variable.isStatic) { - add(fact) - } + buildSet { + // Transmit facts on arguments (from 'actual' to 'formal'): + val actualParams = callExpr.args + val formalParams = getArgumentsOf(callee) + for ((formal, actual) in formalParams.zip(actualParams)) { + addAll(transmitTaintArgumentActualToFormal(fact, from = actual, to = formal)) + } - // Transmit facts on return value (from 'returnValue' to 'lhv'): - if (calleeStart is CommonReturnInst && callStatement is CommonAssignInst) { - // Note: returnValue can be null here in some weird cases, e.g. in lambda. - calleeStart.returnValue?.let { returnValue -> + // Transmit facts on instance (from 'instance' to 'this'): + if (callExpr is CommonInstanceCallExpr) { addAll( - transmitTaintReturn( + transmitTaintInstanceToThis( fact = fact, - from = callStatement.lhv, - to = returnValue, + from = callExpr.instance, + to = getThisInstance(callee), ) ) } + + // Transmit facts on static values: + if (fact.variable.isStatic) { + add(fact) + } + + // Transmit facts on return value (from 'returnValue' to 'lhv'): + if (calleeStart is CommonReturnInst && callStatement is CommonAssignInst) { + // Note: returnValue can be null here in some weird cases, e.g. in lambda. + calleeStart.returnValue?.let { returnValue -> + addAll( + transmitTaintReturn( + fact = fact, + from = callStatement.lhv, + to = returnValue, + ) + ) + } + } } } } @@ -664,46 +682,48 @@ class BackwardTaintFlowFunctions( callStatement: Statement, returnSite: Statement, exitStatement: Statement, - ) = FlowFunction { fact -> - if (fact == TaintZeroFact) { - return@FlowFunction listOf(TaintZeroFact) - } - check(fact is Tainted) - - val callExpr = getCallExpr(callStatement) - ?: error("Call statement should have non-null callExpr") - val callee = graph.methodOf(exitStatement) + ) = with(traits) { + FlowFunction { fact -> + if (fact == TaintZeroFact) { + return@FlowFunction listOf(TaintZeroFact) + } + check(fact is Tainted) + + val callExpr = getCallExpr(callStatement) + ?: error("Call statement should have non-null callExpr") + val callee = graph.methodOf(exitStatement) + + buildSet { + // Transmit facts on arguments (from 'formal' back to 'actual'), if they are passed by-ref: + if (fact.variable.isOnHeap) { + val actualParams = callExpr.args + val formalParams = getArgumentsOf(callee) + for ((formal, actual) in formalParams.zip(actualParams)) { + addAll( + transmitTaintArgumentFormalToActual( + fact = fact, + from = formal, + to = actual, + ) + ) + } + } - buildSet { - // Transmit facts on arguments (from 'formal' back to 'actual'), if they are passed by-ref: - if (fact.variable.isOnHeap) { - val actualParams = callExpr.args - val formalParams = getArgumentsOf(callee) - for ((formal, actual) in formalParams.zip(actualParams)) { + // Transmit facts on instance (from 'this' to 'instance'): + if (callExpr is CommonInstanceCallExpr) { addAll( - transmitTaintArgumentFormalToActual( + transmitTaintThisToInstance( fact = fact, - from = formal, - to = actual, + from = getThisInstance(callee), + to = callExpr.instance, ) ) } - } - // Transmit facts on instance (from 'this' to 'instance'): - if (callExpr is CommonInstanceCallExpr) { - addAll( - transmitTaintThisToInstance( - fact = fact, - from = getThisInstance(callee), - to = callExpr.instance, - ) - ) - } - - // Transmit facts on static values: - if (fact.variable.isStatic) { - add(fact) + // Transmit facts on static values: + if (fact.variable.isStatic) { + add(fact) + } } } } diff --git a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/taint/TaintManager.kt b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/taint/TaintManager.kt index c5bc19f4c4..b42469fd53 100644 --- a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/taint/TaintManager.kt +++ b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/taint/TaintManager.kt @@ -29,6 +29,10 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull +import org.jacodb.api.common.CommonMethod +import org.jacodb.api.common.analysis.ApplicationGraph +import org.jacodb.api.common.cfg.CommonInst +import org.jacodb.taint.configuration.TaintConfigurationItem import org.usvm.dataflow.graph.reversed import org.usvm.dataflow.ifds.ControlEvent import org.usvm.dataflow.ifds.IfdsResult @@ -43,21 +47,16 @@ import org.usvm.dataflow.ifds.UnknownUnit import org.usvm.dataflow.ifds.Vertex import org.usvm.dataflow.util.Traits import org.usvm.dataflow.util.getPathEdges -import org.jacodb.api.common.CommonMethod -import org.jacodb.api.common.analysis.ApplicationGraph -import org.jacodb.api.common.cfg.CommonInst -import org.jacodb.taint.configuration.TaintConfigurationItem import java.util.concurrent.ConcurrentHashMap import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds import kotlin.time.DurationUnit -import kotlin.time.ExperimentalTime import kotlin.time.TimeSource private val logger = mu.KotlinLogging.logger {} -context(Traits) open class TaintManager( + private val traits: Traits, protected val graph: ApplicationGraph, protected val unitResolver: UnitResolver, private val useBidiRunner: Boolean = false, @@ -91,8 +90,9 @@ open class TaintManager( unitResolver = unitResolver, unit = unit, { manager -> - val analyzer = TaintAnalyzer(graph, getConfigForMethod) + val analyzer = TaintAnalyzer(traits, graph, getConfigForMethod) UniRunner( + traits = traits, manager = manager, graph = graph, analyzer = analyzer, @@ -102,8 +102,9 @@ open class TaintManager( ) }, { manager -> - val analyzer = BackwardTaintAnalyzer(graph) + val analyzer = BackwardTaintAnalyzer(traits, graph) UniRunner( + traits = traits, manager = manager, graph = graph.reversed, analyzer = analyzer, @@ -114,8 +115,9 @@ open class TaintManager( } ) } else { - val analyzer = TaintAnalyzer(graph, getConfigForMethod) + val analyzer = TaintAnalyzer(traits, graph, getConfigForMethod) UniRunner( + traits = traits, manager = this@TaintManager, graph = graph, analyzer = analyzer, diff --git a/usvm-jvm-dataflow/build.gradle.kts b/usvm-jvm-dataflow/build.gradle.kts index a6854f6ff1..e22790a6d9 100644 --- a/usvm-jvm-dataflow/build.gradle.kts +++ b/usvm-jvm-dataflow/build.gradle.kts @@ -1,5 +1,3 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { id("usvm.kotlin-conventions") } @@ -16,6 +14,8 @@ dependencies { implementation(Libs.jacodb_api_common) implementation(Libs.jacodb_api_jvm) implementation(Libs.jacodb_core) + implementation(Libs.jacodb_api_storage) + implementation(Libs.jacodb_storage) implementation(Libs.jacodb_taint_configuration) implementation(Libs.sarif4k) @@ -32,12 +32,6 @@ dependencies { } } -tasks.withType { - kotlinOptions { - freeCompilerArgs += "-Xcontext-receivers" - } -} - publishing { publications { create("maven") { diff --git a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/NpeAnalyzers.kt b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/NpeAnalyzers.kt index 7ab623e0ce..49f080af6f 100644 --- a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/NpeAnalyzers.kt +++ b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/NpeAnalyzers.kt @@ -39,14 +39,14 @@ import org.usvm.dataflow.taint.Tainted private val logger = mu.KotlinLogging.logger {} -context(JcTraits) class NpeAnalyzer( + private val traits: JcTraits, private val graph: JcApplicationGraph, private val getConfigForMethod: (JcMethod) -> List?, ) : Analyzer, JcMethod, JcInst> { override val flowFunctions: ForwardNpeFlowFunctions by lazy { - ForwardNpeFlowFunctions(graph, getConfigForMethod) + ForwardNpeFlowFunctions(traits, graph, getConfigForMethod) } private fun isExitPoint(statement: JcInst): Boolean { @@ -55,51 +55,54 @@ class NpeAnalyzer( override fun handleNewEdge( edge: TaintEdge, - ): List> = buildList { - if (isExitPoint(edge.to.statement)) { - add(NewSummaryEdge(edge)) - } + ): List> = with(traits) { + buildList { + if (isExitPoint(edge.to.statement)) { + add(NewSummaryEdge(edge)) + } - val edgeToFact = edge.to.fact + val edgeToFact = edge.to.fact - if (edgeToFact is Tainted && edgeToFact.mark == TaintMark.NULLNESS) { - if (edgeToFact.variable.isDereferencedAt(edge.to.statement)) { - val message = "NPE" // TODO - val vulnerability = TaintVulnerability(message, sink = edge.to) - logger.info { - val m = graph.methodOf(vulnerability.sink.statement) - "Found sink=${vulnerability.sink} in $m" + if (edgeToFact is Tainted && edgeToFact.mark == TaintMark.NULLNESS) { + if (edgeToFact.variable.isDereferencedAt(traits, edge.to.statement)) { + val message = "NPE" // TODO + val vulnerability = TaintVulnerability(message, sink = edge.to) + logger.info { + val m = graph.methodOf(vulnerability.sink.statement) + "Found sink=${vulnerability.sink} in $m" + } + add(NewVulnerability(vulnerability)) } - add(NewVulnerability(vulnerability)) } - } - run { - val callExpr = getCallExpr(edge.to.statement) ?: return@run - val callee = getCallee(callExpr) + run { + val callExpr = getCallExpr(edge.to.statement) ?: return@run + val callee = getCallee(callExpr) - val config = getConfigForMethod(callee) ?: return@run + val config = getConfigForMethod(callee) ?: return@run - // TODO: not always we want to skip sinks on Zero facts. - // Some rules might have ConstantTrue or just true (when evaluated with Zero fact) condition. - if (edgeToFact !is Tainted) { - return@run - } + // TODO: not always we want to skip sinks on Zero facts. + // Some rules might have ConstantTrue or just true (when evaluated with Zero fact) condition. + if (edgeToFact !is Tainted) { + return@run + } - // Determine whether 'edge.to' is a sink via config: - val conditionEvaluator = FactAwareConditionEvaluator( - edgeToFact, - CallPositionToValueResolver(edge.to.statement), - ) - for (item in config.filterIsInstance()) { - if (item.condition.accept(conditionEvaluator)) { - val message = item.ruleNote - val vulnerability = TaintVulnerability(message, sink = edge.to, rule = item) - logger.trace { - val m = graph.methodOf(vulnerability.sink.statement) - "Found sink=${vulnerability.sink} in $m on $item" + // Determine whether 'edge.to' is a sink via config: + val conditionEvaluator = FactAwareConditionEvaluator( + traits, + edgeToFact, + CallPositionToValueResolver(traits, edge.to.statement), + ) + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + val message = item.ruleNote + val vulnerability = TaintVulnerability(message, sink = edge.to, rule = item) + logger.trace { + val m = graph.methodOf(vulnerability.sink.statement) + "Found sink=${vulnerability.sink} in $m on $item" + } + add(NewVulnerability(vulnerability)) } - add(NewVulnerability(vulnerability)) } } } diff --git a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/NpeFlowFunctions.kt b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/NpeFlowFunctions.kt index 9de1f198d0..dc61e8e4ad 100644 --- a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/NpeFlowFunctions.kt +++ b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/NpeFlowFunctions.kt @@ -73,8 +73,8 @@ import org.usvm.dataflow.util.startsWith private val logger = mu.KotlinLogging.logger {} -context(JcTraits) class ForwardNpeFlowFunctions( + private val traits: JcTraits, private val graph: JcApplicationGraph, private val getConfigForMethod: (JcMethod) -> List?, ) : FlowFunctions { @@ -84,15 +84,17 @@ class ForwardNpeFlowFunctions( override fun obtainPossibleStartFacts( method: JcMethod, - ): Collection = buildSet { - addAll(obtainPossibleStartFactsBasic(method)) - - // Possibly null arguments: - for (p in method.parameters.filter { it.isNullable != false }) { - val t = cp.findType(p.type.typeName) - val arg = JcArgument.of(p.index, p.name, t) - val path = convertToPath(arg) - add(Tainted(path, TaintMark.NULLNESS)) + ): Collection = with(traits) { + buildSet { + addAll(obtainPossibleStartFactsBasic(method)) + + // Possibly null arguments: + for (p in method.parameters.filter { it.isNullable != false }) { + val t = cp.findType(p.type.typeName) + val arg = JcArgument.of(p.index, p.name, t) + val path = convertToPath(arg) + add(Tainted(path, TaintMark.NULLNESS)) + } } } @@ -107,10 +109,11 @@ class ForwardNpeFlowFunctions( if (config != null) { val conditionEvaluator = BasicConditionEvaluator( - EntryPointPositionToValueResolver(method) + traits, + EntryPointPositionToValueResolver(traits, method) ) val actionEvaluator = TaintActionEvaluator( - EntryPointPositionToAccessPathResolver(method) + EntryPointPositionToAccessPathResolver(traits, method) ) // Handle EntryPointSource config items: @@ -132,7 +135,7 @@ class ForwardNpeFlowFunctions( fact: Tainted, from: CommonExpr, to: CommonValue, - ): Collection { + ): Collection = with(traits) { from as JcExpr to as JcValue @@ -196,22 +199,24 @@ class ForwardNpeFlowFunctions( private fun generates( inst: JcInst, - ): Collection = buildList { - if (inst is CommonAssignInst) { - val toPath = convertToPath(inst.lhv as JcValue) - val from = inst.rhv - if (from is JcNullConstant || (from is JcCallExpr && from.method.method.isNullable == true)) { - add(Tainted(toPath, TaintMark.NULLNESS)) - } else if (from is JcNewArrayExpr && (from.type as JcArrayType).elementType.nullable != false) { - val accessors = List((from.type as JcArrayType).dimensions) { ElementAccessor } - val path = toPath + accessors - add(Tainted(path, TaintMark.NULLNESS)) + ): Collection = with(traits) { + buildList { + if (inst is CommonAssignInst) { + val toPath = convertToPath(inst.lhv as JcValue) + val from = inst.rhv + if (from is JcNullConstant || (from is JcCallExpr && from.method.method.isNullable == true)) { + add(Tainted(toPath, TaintMark.NULLNESS)) + } else if (from is JcNewArrayExpr && (from.type as JcArrayType).elementType.nullable != false) { + val accessors = List((from.type as JcArrayType).dimensions) { ElementAccessor } + val path = toPath + accessors + add(Tainted(path, TaintMark.NULLNESS)) + } } } } private val JcIfInst.pathComparedWithNull: AccessPath? - get() { + get() = with(traits) { val expr = condition return if (expr.rhv is JcNullConstant) { convertToPathOrNull(expr.lhv) @@ -227,7 +232,7 @@ class ForwardNpeFlowFunctions( next: JcInst, ) = FlowFunction { fact -> if (fact is Tainted && fact.mark == TaintMark.NULLNESS) { - if (fact.variable.isDereferencedAt(current)) { + if (fact.variable.isDereferencedAt(traits, current)) { return@FlowFunction emptySet() } } @@ -277,20 +282,22 @@ class ForwardNpeFlowFunctions( at: JcInst, from: JcValue, to: JcValue, - ): Collection = buildSet { - if (fact.mark == TaintMark.NULLNESS) { - if (fact.variable.isDereferencedAt(at)) { - return@buildSet + ): Collection = with(traits) { + buildSet { + if (fact.mark == TaintMark.NULLNESS) { + if (fact.variable.isDereferencedAt(traits, at)) { + return@buildSet + } } - } - val fromPath = convertToPath(from) - val toPath = convertToPath(to) + val fromPath = convertToPath(from) + val toPath = convertToPath(to) - val tail = (fact.variable - fromPath) ?: return@buildSet - val newPath = toPath + tail - val newTaint = fact.copy(variable = newPath) - add(newTaint) + val tail = (fact.variable - fromPath) ?: return@buildSet + val newPath = toPath + tail + val newTaint = fact.copy(variable = newPath) + add(newTaint) + } } private fun transmitTaintArgumentActualToFormal( @@ -331,233 +338,239 @@ class ForwardNpeFlowFunctions( override fun obtainCallToReturnSiteFlowFunction( callStatement: JcInst, returnSite: JcInst, // FIXME: unused? - ) = FlowFunction { fact -> - if (fact is Tainted && fact.mark == TaintMark.NULLNESS) { - if (fact.variable.isDereferencedAt(callStatement)) { - return@FlowFunction emptySet() + ) = with(traits) { + FlowFunction { fact -> + if (fact is Tainted && fact.mark == TaintMark.NULLNESS) { + if (fact.variable.isDereferencedAt(traits, callStatement)) { + return@FlowFunction emptySet() + } } - } - val callExpr = getCallExpr(callStatement) - ?: error("Call statement should have non-null callExpr") - - val callee = getCallee(callExpr) - val config = getConfigForMethod(callee) - - if (fact == TaintZeroFact) { - return@FlowFunction buildSet { - add(TaintZeroFact) - - if (callStatement is JcAssignInst) { - val toPath = convertToPath(callStatement.lhv) - val from = callStatement.rhv - if (from is JcNullConstant || (from is JcCallExpr && from.method.method.isNullable == true)) { - add(Tainted(toPath, TaintMark.NULLNESS)) - } else if (from is JcNewArrayExpr && (from.type as JcArrayType).elementType.nullable != false) { - val size = (from.type as JcArrayType).dimensions - val accessors = List(size) { ElementAccessor } - val path = toPath + accessors - add(Tainted(path, TaintMark.NULLNESS)) + val callExpr = getCallExpr(callStatement) + ?: error("Call statement should have non-null callExpr") + + val callee = getCallee(callExpr) + val config = getConfigForMethod(callee) + + if (fact == TaintZeroFact) { + return@FlowFunction buildSet { + add(TaintZeroFact) + + if (callStatement is JcAssignInst) { + val toPath = convertToPath(callStatement.lhv) + val from = callStatement.rhv + if (from is JcNullConstant || (from is JcCallExpr && from.method.method.isNullable == true)) { + add(Tainted(toPath, TaintMark.NULLNESS)) + } else if (from is JcNewArrayExpr && (from.type as JcArrayType).elementType.nullable != false) { + val size = (from.type as JcArrayType).dimensions + val accessors = List(size) { ElementAccessor } + val path = toPath + accessors + add(Tainted(path, TaintMark.NULLNESS)) + } + } + + if (config != null) { + val conditionEvaluator = BasicConditionEvaluator( + traits, + CallPositionToValueResolver(traits, callStatement) + ) + val actionEvaluator = TaintActionEvaluator( + CallPositionToAccessPathResolver(traits, callStatement) + ) + + // Handle MethodSource config items: + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + for (action in item.actionsAfter) { + val result = when (action) { + is AssignMark -> actionEvaluator.evaluate(action) + else -> error("$action is not supported for $item") + } + result.onSome { + addAll(it) + } + } + } + } + } + } + } + check(fact is Tainted) + + val statementPassThrough = taintPassThrough(callStatement) + if (statementPassThrough != null) { + for ((from, to) in statementPassThrough) { + if (convertToPath(from) == fact.variable) { + return@FlowFunction setOf( + fact, + fact.copy(variable = convertToPath(to)) + ) } } - if (config != null) { - val conditionEvaluator = BasicConditionEvaluator( - CallPositionToValueResolver(callStatement) + return@FlowFunction setOf(fact) + } + + if (config != null) { + // FIXME: adhoc + if (callee.enclosingClass.name == "java.lang.StringBuilder" && callee.name == "append") { + // Skip rules for StringBuilder::append in NPE analysis. + } else { + val facts = mutableSetOf() + val conditionEvaluator = FactAwareConditionEvaluator( + traits, + fact, + CallPositionToValueResolver(traits, callStatement) ) val actionEvaluator = TaintActionEvaluator( - CallPositionToAccessPathResolver(callStatement) + CallPositionToAccessPathResolver(traits, callStatement) ) + var defaultBehavior = true - // Handle MethodSource config items: - for (item in config.filterIsInstance()) { + // Handle PassThrough config items: + for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { for (action in item.actionsAfter) { val result = when (action) { - is AssignMark -> actionEvaluator.evaluate(action) + is CopyMark -> actionEvaluator.evaluate(action, fact) + is CopyAllMarks -> actionEvaluator.evaluate(action, fact) + is RemoveMark -> actionEvaluator.evaluate(action, fact) + is RemoveAllMarks -> actionEvaluator.evaluate(action, fact) else -> error("$action is not supported for $item") } result.onSome { - addAll(it) + facts += it + defaultBehavior = false } } } } - } - } - } - check(fact is Tainted) - - val statementPassThrough = taintPassThrough(callStatement) - if (statementPassThrough != null) { - for ((from, to) in statementPassThrough) { - if (convertToPath(from) == fact.variable) { - return@FlowFunction setOf( - fact, - fact.copy(variable = convertToPath(to)) - ) - } - } - - return@FlowFunction setOf(fact) - } - if (config != null) { - // FIXME: adhoc - if (callee.enclosingClass.name == "java.lang.StringBuilder" && callee.name == "append") { - // Skip rules for StringBuilder::append in NPE analysis. - } else { - val facts = mutableSetOf() - val conditionEvaluator = FactAwareConditionEvaluator( - fact, - CallPositionToValueResolver(callStatement) - ) - val actionEvaluator = TaintActionEvaluator( - CallPositionToAccessPathResolver(callStatement) - ) - var defaultBehavior = true - - // Handle PassThrough config items: - for (item in config.filterIsInstance()) { - if (item.condition.accept(conditionEvaluator)) { - for (action in item.actionsAfter) { - val result = when (action) { - is CopyMark -> actionEvaluator.evaluate(action, fact) - is CopyAllMarks -> actionEvaluator.evaluate(action, fact) - is RemoveMark -> actionEvaluator.evaluate(action, fact) - is RemoveAllMarks -> actionEvaluator.evaluate(action, fact) - else -> error("$action is not supported for $item") - } - result.onSome { - facts += it - defaultBehavior = false + // Handle Cleaner config items: + for (item in config.filterIsInstance()) { + if (item.condition.accept(conditionEvaluator)) { + for (action in item.actionsAfter) { + val result = when (action) { + is RemoveMark -> actionEvaluator.evaluate(action, fact) + is RemoveAllMarks -> actionEvaluator.evaluate(action, fact) + else -> error("$action is not supported for $item") + } + result.onSome { + facts += it + defaultBehavior = false + } } } } - } - // Handle Cleaner config items: - for (item in config.filterIsInstance()) { - if (item.condition.accept(conditionEvaluator)) { - for (action in item.actionsAfter) { - val result = when (action) { - is RemoveMark -> actionEvaluator.evaluate(action, fact) - is RemoveAllMarks -> actionEvaluator.evaluate(action, fact) - else -> error("$action is not supported for $item") - } - result.onSome { - facts += it - defaultBehavior = false - } + if (!defaultBehavior) { + if (facts.size > 0) { + logger.trace { "Got ${facts.size} facts from config for $callee: $facts" } } + return@FlowFunction facts + } else { + // Fall back to the default behavior, as if there were no config at all. } } + } - if (!defaultBehavior) { - if (facts.size > 0) { - logger.trace { "Got ${facts.size} facts from config for $callee: $facts" } - } - return@FlowFunction facts - } else { - // Fall back to the default behavior, as if there were no config at all. - } + // FIXME: adhoc for constructors: + if (callee.isConstructor) { + return@FlowFunction listOf(fact) } - } - // FIXME: adhoc for constructors: - if (callee.isConstructor) { - return@FlowFunction listOf(fact) - } + // TODO: CONSIDER REFACTORING THIS + // Default behavior for "analyzable" method calls is to remove ("temporarily") + // all the marks from the 'instance' and arguments, in order to allow them "pass through" + // the callee (when it is going to be analyzed), i.e. through "call-to-start" and + // "exit-to-return" flow functions. + // When we know that we are NOT going to analyze the callee, we do NOT need + // to remove any marks from 'instance' and arguments. + // Currently, "analyzability" of the callee depends on the fact that the callee + // is "accessible" through the JcApplicationGraph::callees(). + if (callee in graph.callees(callStatement)) { + + if (fact.variable.isStatic) { + return@FlowFunction emptyList() + } - // TODO: CONSIDER REFACTORING THIS - // Default behavior for "analyzable" method calls is to remove ("temporarily") - // all the marks from the 'instance' and arguments, in order to allow them "pass through" - // the callee (when it is going to be analyzed), i.e. through "call-to-start" and - // "exit-to-return" flow functions. - // When we know that we are NOT going to analyze the callee, we do NOT need - // to remove any marks from 'instance' and arguments. - // Currently, "analyzability" of the callee depends on the fact that the callee - // is "accessible" through the JcApplicationGraph::callees(). - if (callee in graph.callees(callStatement)) { - - if (fact.variable.isStatic) { - return@FlowFunction emptyList() - } + for (actual in callExpr.args) { + // Possibly tainted actual parameter: + val p = convertToPathOrNull(actual) + if (fact.variable.startsWith(p)) { + return@FlowFunction emptyList() // Will be handled by summary edge + } + } - for (actual in callExpr.args) { - // Possibly tainted actual parameter: - val p = convertToPathOrNull(actual) - if (fact.variable.startsWith(p)) { - return@FlowFunction emptyList() // Will be handled by summary edge + if (callExpr is JcInstanceCallExpr) { + // Possibly tainted instance: + val p = convertToPathOrNull(callExpr.instance) + if (fact.variable.startsWith(p)) { + return@FlowFunction emptyList() // Will be handled by summary edge + } } + } - if (callExpr is JcInstanceCallExpr) { - // Possibly tainted instance: - val p = convertToPathOrNull(callExpr.instance) + if (callStatement is JcAssignInst) { + // Possibly tainted lhv: + val p = convertToPathOrNull(callStatement.lhv) if (fact.variable.startsWith(p)) { - return@FlowFunction emptyList() // Will be handled by summary edge + return@FlowFunction emptyList() // Overridden by rhv } } + // The "most default" behaviour is encapsulated here: + transmitTaintNormal(fact, callStatement) } - - if (callStatement is JcAssignInst) { - // Possibly tainted lhv: - val p = convertToPathOrNull(callStatement.lhv) - if (fact.variable.startsWith(p)) { - return@FlowFunction emptyList() // Overridden by rhv - } - } - - // The "most default" behaviour is encapsulated here: - transmitTaintNormal(fact, callStatement) } override fun obtainCallToStartFlowFunction( callStatement: JcInst, calleeStart: JcInst, - ) = FlowFunction { fact -> - val callee = graph.methodOf(calleeStart) + ) = with(traits) { + FlowFunction { fact -> + val callee = graph.methodOf(calleeStart) - if (fact == TaintZeroFact) { - return@FlowFunction obtainPossibleStartFactsBasic(callee) - } - check(fact is Tainted) + if (fact == TaintZeroFact) { + return@FlowFunction obtainPossibleStartFactsBasic(callee) + } + check(fact is Tainted) - val callExpr = getCallExpr(callStatement) - ?: error("Call statement should have non-null callExpr") + val callExpr = getCallExpr(callStatement) + ?: error("Call statement should have non-null callExpr") - buildSet { - // Transmit facts on arguments (from 'actual' to 'formal'): - val actualParams = callExpr.args - val formalParams = getArgumentsOf(callee) - for ((formal, actual) in formalParams.zip(actualParams)) { - addAll( - transmitTaintArgumentActualToFormal( - fact = fact, - at = callStatement, - from = actual, - to = formal + buildSet { + // Transmit facts on arguments (from 'actual' to 'formal'): + val actualParams = callExpr.args + val formalParams = getArgumentsOf(callee) + for ((formal, actual) in formalParams.zip(actualParams)) { + addAll( + transmitTaintArgumentActualToFormal( + fact = fact, + at = callStatement, + from = actual, + to = formal + ) ) - ) - } + } - // Transmit facts on instance (from 'instance' to 'this'): - if (callExpr is JcInstanceCallExpr) { - addAll( - transmitTaintInstanceToThis( - fact = fact, - at = callStatement, - from = callExpr.instance, - to = getThisInstance(callee) + // Transmit facts on instance (from 'instance' to 'this'): + if (callExpr is JcInstanceCallExpr) { + addAll( + transmitTaintInstanceToThis( + fact = fact, + at = callStatement, + from = callExpr.instance, + to = getThisInstance(callee) + ) ) - ) - } + } - // Transmit facts on static values: - if (fact.variable.isStatic) { - add(fact) + // Transmit facts on static values: + if (fact.variable.isStatic) { + add(fact) + } } } } @@ -566,75 +579,77 @@ class ForwardNpeFlowFunctions( callStatement: JcInst, returnSite: JcInst, // unused exitStatement: JcInst, - ) = FlowFunction { fact -> - // TODO: do we even need to return non-empty list for zero fact here? - if (fact == TaintZeroFact) { - // return@FlowFunction listOf(Zero) - return@FlowFunction buildSet { - add(TaintZeroFact) - if (exitStatement is JcReturnInst && callStatement is JcAssignInst) { - // Note: returnValue can be null here in some weird cases, e.g. in lambda. - exitStatement.returnValue?.let { returnValue -> - if (returnValue is JcNullConstant) { - val toPath = convertToPath(callStatement.lhv) - add(Tainted(toPath, TaintMark.NULLNESS)) + ) = with(traits) { + FlowFunction { fact -> + // TODO: do we even need to return non-empty list for zero fact here? + if (fact == TaintZeroFact) { + // return@FlowFunction listOf(Zero) + return@FlowFunction buildSet { + add(TaintZeroFact) + if (exitStatement is JcReturnInst && callStatement is JcAssignInst) { + // Note: returnValue can be null here in some weird cases, e.g. in lambda. + exitStatement.returnValue?.let { returnValue -> + if (returnValue is JcNullConstant) { + val toPath = convertToPath(callStatement.lhv) + add(Tainted(toPath, TaintMark.NULLNESS)) + } } } } } - } - check(fact is Tainted) - - val callExpr = getCallExpr(callStatement) - ?: error("Call statement should have non-null callExpr") - val callee = graph.methodOf(exitStatement) + check(fact is Tainted) + + val callExpr = getCallExpr(callStatement) + ?: error("Call statement should have non-null callExpr") + val callee = graph.methodOf(exitStatement) + + buildSet { + // Transmit facts on arguments (from 'formal' back to 'actual'), if they are passed by-ref: + if (fact.variable.isOnHeap) { + val actualParams = callExpr.args + val formalParams = getArgumentsOf(callee) + for ((formal, actual) in formalParams.zip(actualParams)) { + addAll( + transmitTaintArgumentFormalToActual( + fact = fact, + at = callStatement, + from = formal, + to = actual + ) + ) + } + } - buildSet { - // Transmit facts on arguments (from 'formal' back to 'actual'), if they are passed by-ref: - if (fact.variable.isOnHeap) { - val actualParams = callExpr.args - val formalParams = getArgumentsOf(callee) - for ((formal, actual) in formalParams.zip(actualParams)) { + // Transmit facts on instance (from 'this' to 'instance'): + if (callExpr is JcInstanceCallExpr) { addAll( - transmitTaintArgumentFormalToActual( + transmitTaintThisToInstance( fact = fact, at = callStatement, - from = formal, - to = actual + from = getThisInstance(callee), + to = callExpr.instance, ) ) } - } - // Transmit facts on instance (from 'this' to 'instance'): - if (callExpr is JcInstanceCallExpr) { - addAll( - transmitTaintThisToInstance( - fact = fact, - at = callStatement, - from = getThisInstance(callee), - to = callExpr.instance, - ) - ) - } - - // Transmit facts on static values: - if (fact.variable.isStatic) { - add(fact) - } + // Transmit facts on static values: + if (fact.variable.isStatic) { + add(fact) + } - // Transmit facts on return value (from 'returnValue' to 'lhv'): - if (exitStatement is JcReturnInst && callStatement is JcAssignInst) { - // Note: returnValue can be null here in some weird cases, e.g. in lambda. - exitStatement.returnValue?.let { returnValue -> - addAll( - transmitTaintReturn( - fact = fact, - at = callStatement, - from = returnValue, - to = callStatement.lhv, + // Transmit facts on return value (from 'returnValue' to 'lhv'): + if (exitStatement is JcReturnInst && callStatement is JcAssignInst) { + // Note: returnValue can be null here in some weird cases, e.g. in lambda. + exitStatement.returnValue?.let { returnValue -> + addAll( + transmitTaintReturn( + fact = fact, + at = callStatement, + from = returnValue, + to = callStatement.lhv, + ) ) - ) + } } } } diff --git a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/NpeManager.kt b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/NpeManager.kt index f9c65c001e..526242c8fe 100644 --- a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/NpeManager.kt +++ b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/NpeManager.kt @@ -33,20 +33,21 @@ import org.usvm.dataflow.taint.TaintZeroFact private val logger = mu.KotlinLogging.logger {} -context(JcTraits) class NpeManager( + private val traits: JcTraits, graph: JcApplicationGraph, unitResolver: UnitResolver, private val getConfigForMethod: (JcMethod) -> List?, -) : TaintManager(graph, unitResolver, useBidiRunner = false, getConfigForMethod) { +) : TaintManager(traits, graph, unitResolver, useBidiRunner = false, getConfigForMethod) { override fun newRunner( unit: UnitType, ): TaintRunner { check(unit !in runnerForUnit) { "Runner for $unit already exists" } - val analyzer = NpeAnalyzer(graph as JcApplicationGraph, getConfigForMethod) + val analyzer = NpeAnalyzer(traits, graph as JcApplicationGraph, getConfigForMethod) val runner = UniRunner( + traits = traits, graph = graph, analyzer = analyzer, manager = this@NpeManager, @@ -81,5 +82,5 @@ fun jcNpeManager( return@run { method: JcMethod -> taintConfigurationFeature?.getConfigForMethod(method) } } - NpeManager(graph, unitResolver, config) + NpeManager(traits = this, graph, unitResolver, config) } diff --git a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/Utils.kt b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/Utils.kt index 7c89966eef..da97dca2e8 100644 --- a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/Utils.kt +++ b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/npe/Utils.kt @@ -26,33 +26,31 @@ import org.usvm.dataflow.ifds.minus import org.usvm.dataflow.jvm.util.JcTraits import org.usvm.dataflow.util.startsWith -context(JcTraits) -internal fun AccessPath?.isDereferencedAt(expr: JcExpr): Boolean { - if (this == null) { +internal fun AccessPath?.isDereferencedAt(traits: JcTraits, expr: JcExpr): Boolean = with(traits) { + if (this@isDereferencedAt == null) { return false } if (expr is JcInstanceCallExpr) { val instancePath = convertToPathOrNull(expr.instance) - if (instancePath.startsWith(this)) { + if (instancePath.startsWith(this@isDereferencedAt)) { return true } } if (expr is JcLengthExpr) { val arrayPath = convertToPathOrNull(expr.array) - if (arrayPath.startsWith(this)) { + if (arrayPath.startsWith(this@isDereferencedAt)) { return true } } return expr.values .mapNotNull { convertToPathOrNull(it) } - .any { (it - this)?.isNotEmpty() == true } + .any { (it - this@isDereferencedAt)?.isNotEmpty() == true } } -context(JcTraits) -internal fun AccessPath?.isDereferencedAt(inst: JcInst): Boolean { +internal fun AccessPath?.isDereferencedAt(traits: JcTraits, inst: JcInst): Boolean { if (this == null) return false - return inst.operands.any { isDereferencedAt(it) } + return inst.operands.any { isDereferencedAt(traits, it) } } diff --git a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/taint/TaintManager.kt b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/taint/TaintManager.kt index d07548dd40..a08288c213 100644 --- a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/taint/TaintManager.kt +++ b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/taint/TaintManager.kt @@ -23,5 +23,5 @@ fun jcTaintManager( return@run { method: JcMethod -> taintConfigurationFeature?.getConfigForMethod(method) } } - TaintManager(graph, unitResolver, useBidiRunner, config) + TaintManager(traits = this, graph, unitResolver, useBidiRunner, config) } diff --git a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/UnusedVariableAnalyzer.kt b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/UnusedVariableAnalyzer.kt index 667bbf963a..843a7e6a75 100644 --- a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/UnusedVariableAnalyzer.kt +++ b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/UnusedVariableAnalyzer.kt @@ -16,23 +16,23 @@ package org.usvm.dataflow.jvm.unused +import org.jacodb.api.common.CommonMethod +import org.jacodb.api.common.analysis.ApplicationGraph +import org.jacodb.api.common.cfg.CommonInst import org.usvm.dataflow.ifds.Analyzer import org.usvm.dataflow.ifds.Edge import org.usvm.dataflow.ifds.Vertex import org.usvm.dataflow.util.Traits -import org.jacodb.api.common.CommonMethod -import org.jacodb.api.common.analysis.ApplicationGraph -import org.jacodb.api.common.cfg.CommonInst -context(Traits) class UnusedVariableAnalyzer( + private val traits: Traits, private val graph: ApplicationGraph, ) : Analyzer, Method, Statement> where Method : CommonMethod, Statement : CommonInst { override val flowFunctions: UnusedVariableFlowFunctions by lazy { - UnusedVariableFlowFunctions(graph) + UnusedVariableFlowFunctions(traits, graph) } private fun isExitPoint(statement: Statement): Boolean { diff --git a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/UnusedVariableFlowFunctions.kt b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/UnusedVariableFlowFunctions.kt index 4612a339f6..711da46cf7 100644 --- a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/UnusedVariableFlowFunctions.kt +++ b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/UnusedVariableFlowFunctions.kt @@ -27,8 +27,8 @@ import org.usvm.dataflow.ifds.FlowFunctions import org.usvm.dataflow.ifds.isOnHeap import org.usvm.dataflow.util.Traits -context(Traits) class UnusedVariableFlowFunctions( + private val traits: Traits, private val graph: ApplicationGraph, ) : FlowFunctions where Method : CommonMethod, @@ -43,35 +43,37 @@ class UnusedVariableFlowFunctions( override fun obtainSequentFlowFunction( current: Statement, next: Statement, - ) = FlowFunction { fact -> - if (current !is CommonAssignInst) { - return@FlowFunction setOf(fact) - } + ) = with(traits) { + FlowFunction { fact -> + if (current !is CommonAssignInst) { + return@FlowFunction setOf(fact) + } - if (fact == UnusedVariableZeroFact) { - val toPath = convertToPath(current.lhv) - if (!toPath.isOnHeap) { - return@FlowFunction setOf(UnusedVariableZeroFact, UnusedVariable(toPath, current)) - } else { - return@FlowFunction setOf(UnusedVariableZeroFact) + if (fact == UnusedVariableZeroFact) { + val toPath = convertToPath(current.lhv) + if (!toPath.isOnHeap) { + return@FlowFunction setOf(UnusedVariableZeroFact, UnusedVariable(toPath, current)) + } else { + return@FlowFunction setOf(UnusedVariableZeroFact) + } } - } - check(fact is UnusedVariable) + check(fact is UnusedVariable) - val toPath = convertToPath(current.lhv) - val default = if (toPath == fact.variable) emptySet() else setOf(fact) - val fromPath = convertToPathOrNull(current.rhv) - ?: return@FlowFunction default + val toPath = convertToPath(current.lhv) + val default = if (toPath == fact.variable) emptySet() else setOf(fact) + val fromPath = convertToPathOrNull(current.rhv) + ?: return@FlowFunction default - if (fromPath.isOnHeap || toPath.isOnHeap) { - return@FlowFunction default - } + if (fromPath.isOnHeap || toPath.isOnHeap) { + return@FlowFunction default + } - if (fromPath == fact.variable) { - return@FlowFunction default + fact.copy(variable = toPath) - } + if (fromPath == fact.variable) { + return@FlowFunction default + fact.copy(variable = toPath) + } - default + default + } } override fun obtainCallToReturnSiteFlowFunction( @@ -82,27 +84,29 @@ class UnusedVariableFlowFunctions( override fun obtainCallToStartFlowFunction( callStatement: Statement, calleeStart: Statement, - ) = FlowFunction { fact -> - val callExpr = getCallExpr(callStatement) - ?: error("Call statement should have non-null callExpr") + ) = with(traits) { + FlowFunction { fact -> + val callExpr = getCallExpr(callStatement) + ?: error("Call statement should have non-null callExpr") - if (fact == UnusedVariableZeroFact) { - // FIXME: use common? - if (callExpr !is JcStaticCallExpr && callExpr !is JcSpecialCallExpr) { - return@FlowFunction setOf(UnusedVariableZeroFact) - } - return@FlowFunction buildSet { - add(UnusedVariableZeroFact) - val callee = graph.methodOf(calleeStart) - val formalParams = getArgumentsOf(callee) - for (formal in formalParams) { - add(UnusedVariable(convertToPath(formal), callStatement)) + if (fact == UnusedVariableZeroFact) { + // FIXME: use common? + if (callExpr !is JcStaticCallExpr && callExpr !is JcSpecialCallExpr) { + return@FlowFunction setOf(UnusedVariableZeroFact) + } + return@FlowFunction buildSet { + add(UnusedVariableZeroFact) + val callee = graph.methodOf(calleeStart) + val formalParams = getArgumentsOf(callee) + for (formal in formalParams) { + add(UnusedVariable(convertToPath(formal), callStatement)) + } } } - } - check(fact is UnusedVariable) + check(fact is UnusedVariable) - emptySet() + emptySet() + } } override fun obtainExitToReturnSiteFlowFunction( diff --git a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/UnusedVariableManager.kt b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/UnusedVariableManager.kt index 09c693e9de..16ffc2babf 100644 --- a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/UnusedVariableManager.kt +++ b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/UnusedVariableManager.kt @@ -29,6 +29,9 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull +import org.jacodb.api.common.CommonMethod +import org.jacodb.api.common.analysis.ApplicationGraph +import org.jacodb.api.common.cfg.CommonInst import org.usvm.dataflow.ifds.ControlEvent import org.usvm.dataflow.ifds.Edge import org.usvm.dataflow.ifds.Manager @@ -42,9 +45,6 @@ import org.usvm.dataflow.ifds.UnknownUnit import org.usvm.dataflow.ifds.Vertex import org.usvm.dataflow.util.Traits import org.usvm.dataflow.util.getPathEdges -import org.jacodb.api.common.CommonMethod -import org.jacodb.api.common.analysis.ApplicationGraph -import org.jacodb.api.common.cfg.CommonInst import java.util.concurrent.ConcurrentHashMap import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -53,8 +53,8 @@ import kotlin.time.TimeSource private val logger = mu.KotlinLogging.logger {} -context(Traits) class UnusedVariableManager( + private val traits: Traits, private val graph: ApplicationGraph, private val unitResolver: UnitResolver, ) : Manager, Method, Statement> @@ -75,8 +75,9 @@ class UnusedVariableManager( check(unit !in runnerForUnit) { "Runner for $unit already exists" } logger.debug { "Creating a new runner for $unit" } - val analyzer = UnusedVariableAnalyzer(graph) + val analyzer = UnusedVariableAnalyzer(traits, graph) val runner = UniRunner( + traits = traits, graph = graph, analyzer = analyzer, manager = this@UnusedVariableManager, @@ -193,7 +194,7 @@ class UnusedVariableManager( if (fact is UnusedVariable) { @Suppress("UNCHECKED_CAST") used.putIfAbsent(fact.initStatement as Statement, false) - if (fact.variable.isUsedAt(inst)) { + if (fact.variable.isUsedAt(traits, inst)) { used[fact.initStatement] = true } } diff --git a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/Utils.kt b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/Utils.kt index 3571cdfbfd..7f60290ff8 100644 --- a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/Utils.kt +++ b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/unused/Utils.kt @@ -29,41 +29,41 @@ import org.jacodb.api.jvm.cfg.JcTerminatingInst import org.usvm.dataflow.ifds.AccessPath import org.usvm.dataflow.util.Traits -context(Traits) internal fun AccessPath.isUsedAt( + traits: Traits, expr: CommonExpr, -): Boolean { +): Boolean = with(traits) { return getValues(expr).any { - convertToPathOrNull(it) == this + convertToPathOrNull(it) == this@isUsedAt } } -context(Traits) internal fun AccessPath.isUsedAt( + traits: Traits, inst: CommonInst, -): Boolean { +): Boolean = with(traits) { val callExpr = getCallExpr(inst) if (callExpr != null) { // Don't count constructor calls as usages if (callExpr is JcSpecialCallExpr && callExpr.method.method.isConstructor - && isUsedAt(callExpr.instance) + && isUsedAt(traits, callExpr.instance) ) { return false } - return isUsedAt(callExpr) + return isUsedAt(traits, callExpr) } if (inst is JcAssignInst) { - if (inst.lhv is JcArrayAccess && isUsedAt(inst.lhv)) { + if (inst.lhv is JcArrayAccess && isUsedAt(traits, inst.lhv)) { return true } - return isUsedAt(inst.rhv) && (inst.lhv !is JcLocal || inst.rhv !is JcLocal) + return isUsedAt(traits, inst.rhv) && (inst.lhv !is JcLocal || inst.rhv !is JcLocal) } if (inst is JcTerminatingInst || inst is JcBranchingInst) { inst as JcInst - return inst.operands.any { isUsedAt(it) } + return inst.operands.any { isUsedAt(traits, it) } } return false } diff --git a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/ConditionEvaluatorTest.kt b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/ConditionEvaluatorTest.kt index 1b58239773..2141b9b026 100644 --- a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/ConditionEvaluatorTest.kt +++ b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/ConditionEvaluatorTest.kt @@ -93,9 +93,7 @@ class ConditionEvaluatorTest { else -> null }.toMaybe() } - private val evaluator: ConditionVisitor = with(traits) { - BasicConditionEvaluator(positionResolver) - } + private val evaluator: ConditionVisitor = BasicConditionEvaluator(traits, positionResolver) @Test fun `True is true`() { @@ -331,7 +329,7 @@ class ConditionEvaluatorTest { fun `FactAwareConditionEvaluator supports ContainsMark`() { with(traits) { val fact = Tainted(convertToPath(intValue), TaintMark("FOO")) - val factAwareEvaluator = FactAwareConditionEvaluator(fact, positionResolver) + val factAwareEvaluator = FactAwareConditionEvaluator(traits, fact, positionResolver) assertTrue(factAwareEvaluator.visit(ContainsMark(intArg, TaintMark("FOO")))) assertFalse(factAwareEvaluator.visit(ContainsMark(intArg, TaintMark("BAR")))) assertFalse(factAwareEvaluator.visit(ContainsMark(stringArg, TaintMark("FOO")))) diff --git a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsSqlTest.kt b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsSqlTest.kt index 2f10ed7f45..300967417b 100644 --- a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsSqlTest.kt +++ b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsSqlTest.kt @@ -98,7 +98,7 @@ class IfdsSqlTest : BaseAnalysisTest() { val trace = traceGraph.getAllTraces().first() assertTrue(trace.isNotEmpty()) val sarif = with(JcTraits(graph.cp)) { - sarifReportFromVulnerabilities(listOf(sink.toSarif(traceGraph))) + sarifReportFromVulnerabilities(traits = this, listOf(sink.toSarif(traceGraph))) } val json = Json { prettyPrint = true } diff --git a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsUnusedTest.kt b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsUnusedTest.kt index 3770129a85..1eeb3a2a61 100644 --- a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsUnusedTest.kt +++ b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsUnusedTest.kt @@ -61,7 +61,7 @@ class IfdsUnusedTest : BaseAnalysisTest() { testSingleJulietClass(className) { method -> val unitResolver = SingletonUnitResolver val manager = with(JcTraits(cp)) { - UnusedVariableManager(graph, unitResolver) + UnusedVariableManager(traits = this, graph, unitResolver) } manager.analyze(listOf(method), timeout = 30.seconds) } @@ -75,7 +75,7 @@ class IfdsUnusedTest : BaseAnalysisTest() { val badMethod = clazz.methods.single { it.name == "bad" } val unitResolver = SingletonUnitResolver val manager = with(JcTraits(cp)) { - UnusedVariableManager(graph, unitResolver) + UnusedVariableManager(traits = this, graph, unitResolver) } val sinks = manager.analyze(listOf(badMethod), timeout = 30.seconds) Assertions.assertTrue(sinks.isNotEmpty()) diff --git a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/JodaDateTimeAnalysisTest.kt b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/JodaDateTimeAnalysisTest.kt index 2615df0ea6..15ca04eefc 100644 --- a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/JodaDateTimeAnalysisTest.kt +++ b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/JodaDateTimeAnalysisTest.kt @@ -59,7 +59,7 @@ class JodaDateTimeAnalysisTest : BaseAnalysisTest() { val methods = clazz.declaredMethods val unitResolver = SingletonUnitResolver val manager = with(JcTraits(cp)) { - UnusedVariableManager(graph, unitResolver) + UnusedVariableManager(traits = this, graph, unitResolver) } val sinks = manager.analyze(methods, timeout = 60.seconds) logger.info { "Unused variables found: ${sinks.size}" } diff --git a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/TaintFlowFunctionsTest.kt b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/TaintFlowFunctionsTest.kt index c6e1c59622..cf5c1abb51 100644 --- a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/TaintFlowFunctionsTest.kt +++ b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/TaintFlowFunctionsTest.kt @@ -95,7 +95,7 @@ open class TaintFlowFunctionsTest : BaseAnalysisTest(configFileName = "config_te @Test fun `test obtain start facts`() { with(JcTraits(cp)) { - val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) + val flowSpace = ForwardTaintFlowFunctions(traits = this, graph, getConfigForMethod) val facts = flowSpace.obtainPossibleStartFacts(testMethod).toList() val arg0 = getArgument(testMethod.parameters[0])!! val arg0Taint = Tainted(arg0.toPath(), TaintMark("EXAMPLE")) @@ -110,7 +110,7 @@ open class TaintFlowFunctionsTest : BaseAnalysisTest(configFileName = "config_te val x: JcLocal = JcLocalVar(1, "x", stringType) val y: JcLocal = JcLocalVar(2, "y", stringType) val inst = JcAssignInst(location = mockk(), lhv = x, rhv = y) - val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) + val flowSpace = ForwardTaintFlowFunctions(traits = this, graph, getConfigForMethod) val f = flowSpace.obtainSequentFlowFunction(inst, next = mockk()) val yTaint = Tainted(y.toPath(), TaintMark("TAINT")) val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) @@ -127,7 +127,7 @@ open class TaintFlowFunctionsTest : BaseAnalysisTest(configFileName = "config_te val callStatement = JcAssignInst(location = mockk(), lhv = x, rhv = mockk { every { callee } returns testMethod }) - val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) + val flowSpace = ForwardTaintFlowFunctions(traits = this, graph, getConfigForMethod) val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) val xTaint = Tainted(x.toPath(), TaintMark("EXAMPLE")) val facts = f.compute(TaintZeroFact).toList() @@ -144,7 +144,7 @@ open class TaintFlowFunctionsTest : BaseAnalysisTest(configFileName = "config_te every { callee } returns testMethod every { args } returns listOf(x) }) - val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) + val flowSpace = ForwardTaintFlowFunctions(traits = this, graph, getConfigForMethod) val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) val xTaint = Tainted(x.toPath(), TaintMark("REMOVE")) val facts = f.compute(xTaint).toList() @@ -162,7 +162,7 @@ open class TaintFlowFunctionsTest : BaseAnalysisTest(configFileName = "config_te every { callee } returns testMethod every { args } returns listOf(x) }) - val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) + val flowSpace = ForwardTaintFlowFunctions(traits = this, graph, getConfigForMethod) val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) val xTaint = Tainted(x.toPath(), TaintMark("COPY")) val yTaint = Tainted(y.toPath(), TaintMark("COPY")) @@ -184,7 +184,7 @@ open class TaintFlowFunctionsTest : BaseAnalysisTest(configFileName = "config_te every { callee } returns testMethod every { args } returns listOf(x) }) - val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) + val flowSpace = ForwardTaintFlowFunctions(traits = this, graph, getConfigForMethod) val f = flowSpace.obtainCallToStartFlowFunction(callStatement, calleeStart = mockk { every { location } returns mockk { every { method } returns testMethod @@ -214,7 +214,7 @@ open class TaintFlowFunctionsTest : BaseAnalysisTest(configFileName = "config_te val exitStatement = JcReturnInst(location = mockk { every { method } returns testMethod }, returnValue = y) - val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) + val flowSpace = ForwardTaintFlowFunctions(traits = this, graph, getConfigForMethod) val f = flowSpace.obtainExitToReturnSiteFlowFunction(callStatement, returnSite = mockk(), exitStatement) val yTaint = Tainted(y.toPath(), TaintMark("TAINT")) val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) diff --git a/usvm-jvm-instrumentation/build.gradle.kts b/usvm-jvm-instrumentation/build.gradle.kts index 72349afadf..fa9b31d188 100644 --- a/usvm-jvm-instrumentation/build.gradle.kts +++ b/usvm-jvm-instrumentation/build.gradle.kts @@ -67,7 +67,7 @@ dependencies { } tasks.withType { - kotlinOptions { + compilerOptions { allWarningsAsErrors = false } } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/transformers/JcSingleInstructionTransformer.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/transformers/JcSingleInstructionTransformer.kt index b788672e4e..1f32920656 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/transformers/JcSingleInstructionTransformer.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/transformers/JcSingleInstructionTransformer.kt @@ -99,7 +99,12 @@ class JcSingleInstructionTransformer(originalInstructions: JcInstList) { } } + @OptIn(ExperimentalContracts::class) inline fun MutableList.addInstruction(origin: JcInstLocation, body: (JcInstLocation) -> JcInst) { + contract { + callsInPlace(body, InvocationKind.EXACTLY_ONCE) + } + val index = size val newLocation = JcInstLocationImpl(origin.method, index, origin.lineNumber) val instruction = body(newLocation) diff --git a/usvm-jvm/src/test/kotlin/org/usvm/samples/collections/QueueUsagesTest.kt b/usvm-jvm/src/test/kotlin/org/usvm/samples/collections/QueueUsagesTest.kt index 18c9ee5318..b1b0c8567e 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/samples/collections/QueueUsagesTest.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/samples/collections/QueueUsagesTest.kt @@ -90,8 +90,8 @@ class QueueUsagesTest : JavaMethodTestRunner() { eq(4), { _, q, r -> q == null && r == 0 }, { _, q, r -> q is LinkedList<*> && r == 1 }, - { _, q, r -> q is ArrayDeque<*> && r == 2 }, - { _, q, r -> q !is LinkedList<*> && q !is ArrayDeque<*> && r == 3 } + { _, q, r -> q is java.util.ArrayDeque<*> && r == 2 }, + { _, q, r -> q !is LinkedList<*> && q !is java.util.ArrayDeque<*> && r == 3 } ) } @@ -102,8 +102,8 @@ class QueueUsagesTest : JavaMethodTestRunner() { eq(4), { _, q, r -> q == null && r == 0 }, { _, q, r -> q is LinkedList<*> && r == 1 }, - { _, q, r -> q is ArrayDeque<*> && r == 2 }, - { _, q, r -> q !is LinkedList<*> && q !is ArrayDeque<*> && r == 3 } // this is uncovered + { _, q, r -> q is java.util.ArrayDeque<*> && r == 2 }, + { _, q, r -> q !is LinkedList<*> && q !is java.util.ArrayDeque<*> && r == 3 } // this is uncovered ) } diff --git a/usvm-util/src/main/kotlin/org/usvm/algorithms/WeightedAaTree.kt b/usvm-util/src/main/kotlin/org/usvm/algorithms/WeightedAaTree.kt index f29afe3e12..2d48201b4f 100644 --- a/usvm-util/src/main/kotlin/org/usvm/algorithms/WeightedAaTree.kt +++ b/usvm-util/src/main/kotlin/org/usvm/algorithms/WeightedAaTree.kt @@ -1,6 +1,6 @@ package org.usvm.algorithms -import java.util.* +import java.util.Stack import kotlin.math.min data class AaTreeNode( @@ -139,30 +139,28 @@ class WeightedAaTree(private val comparator: Comparator) { val compareResult = comparator.compare(value, deleteFrom.value) - return when { - compareResult < 0 -> - removeRec(value, deleteFrom.left) { k(deleteFrom.update(left = it).balanceAfterRemove()) } - compareResult > 0 -> - removeRec(value, deleteFrom.right) { k(deleteFrom.update(right = it).balanceAfterRemove()) } - else -> { - when { - deleteFrom.right == null && deleteFrom.level == 1 -> { - count-- - k(null) - } - deleteFrom.left == null -> { - val succ = deleteFrom.succ() - checkNotNull(succ) - removeRec(succ.value, deleteFrom.right) { k(deleteFrom.update(value = succ.value, right = it).balanceAfterRemove()) } - } - else -> { - val pred = deleteFrom.pred() - checkNotNull(pred) - removeRec(pred.value, deleteFrom.left) { k(deleteFrom.update(value = pred.value, left = it).balanceAfterRemove()) } - } - } - } + if (compareResult < 0) { + return removeRec(value, deleteFrom.left) { k(deleteFrom.update(left = it).balanceAfterRemove()) } + } + + if (compareResult > 0) { + return removeRec(value, deleteFrom.right) { k(deleteFrom.update(right = it).balanceAfterRemove()) } + } + + if (deleteFrom.right == null && deleteFrom.level == 1) { + count-- + return k(null) } + + if (deleteFrom.left == null) { + val succ = deleteFrom.succ() + checkNotNull(succ) + return removeRec(succ.value, deleteFrom.right) { k(deleteFrom.update(value = succ.value, right = it).balanceAfterRemove()) } + } + + val pred = deleteFrom.pred() + checkNotNull(pred) + return removeRec(pred.value, deleteFrom.left) { k(deleteFrom.update(value = pred.value, left = it).balanceAfterRemove()) } } /**