From 37bd954240656c6f4ef78d732004ec3d81271797 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 17 Jul 2024 18:47:58 +0300 Subject: [PATCH 1/9] Update jacodb and refine dataflow accordingly --- settings.gradle.kts | 2 +- usvm-dataflow/build.gradle.kts | 6 +- .../org/usvm/dataflow/config/Condition.kt | 24 +- .../org/usvm/dataflow/config/Position.kt | 35 ++- .../org/usvm/dataflow/graph/BackwardGraphs.kt | 4 - .../kotlin/org/usvm/dataflow/ifds/Runner.kt | 2 +- .../kotlin/org/usvm/dataflow/sarif/Sarif.kt | 6 +- .../org/usvm/dataflow/taint/TaintAnalyzers.kt | 28 +-- .../usvm/dataflow/taint/TaintFlowFunctions.kt | 121 +++++----- .../kotlin/org/usvm/dataflow/util/Traits.kt | 68 +++--- usvm-jvm-dataflow/build.gradle.kts | 4 +- .../jvm/graph/JcApplicationGraphImpl.kt | 2 +- .../jvm/graph/SimplifiedJcApplicationGraph.kt | 2 +- .../org/usvm/dataflow/jvm/npe/NpeAnalyzers.kt | 18 +- .../usvm/dataflow/jvm/npe/NpeFlowFunctions.kt | 110 +++++---- .../org/usvm/dataflow/jvm/npe/NpeManager.kt | 16 +- .../kotlin/org/usvm/dataflow/jvm/npe/Utils.kt | 31 ++- .../usvm/dataflow/jvm/taint/TaintManager.kt | 11 +- .../jvm/unused/UnusedVariableFlowFunctions.kt | 24 +- .../org/usvm/dataflow/jvm/unused/Utils.kt | 10 +- .../org/usvm/dataflow/jvm/util/JcTraits.kt | 219 ++++++++---------- .../jvm/impl/ConditionEvaluatorTest.kt | 23 +- .../org/usvm/dataflow/jvm/impl/IfdsNpeTest.kt | 1 + .../org/usvm/dataflow/jvm/impl/IfdsSqlTest.kt | 9 +- .../jvm/impl/IfdsUntrustedLoopBoundTest.kt | 1 + .../usvm/dataflow/jvm/impl/IfdsUnusedTest.kt | 9 +- .../jvm/impl/JodaDateTimeAnalysisTest.kt | 5 +- .../jvm/impl/TaintFlowFunctionsTest.kt | 200 ++++++++-------- .../org/usvm/api/util/JcTestDecoders.kt | 2 +- .../org/usvm/machine/JcApplicationGraph.kt | 6 +- .../kotlin/org/usvm/machine/JcTypeSystem.kt | 7 +- .../approximations/ApproximationsTest.kt | 2 +- 32 files changed, 503 insertions(+), 505 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index fc8382e329..e2dbbba1c3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,4 +28,4 @@ pluginManagement { } } } -} \ No newline at end of file +} diff --git a/usvm-dataflow/build.gradle.kts b/usvm-dataflow/build.gradle.kts index 08e9f5bb10..ac3d9dd45c 100644 --- a/usvm-dataflow/build.gradle.kts +++ b/usvm-dataflow/build.gradle.kts @@ -8,16 +8,14 @@ dependencies { implementation("${Versions.jacodbPackage}:jacodb-api-common:${Versions.jacodb}") implementation("${Versions.jacodbPackage}:jacodb-taint-configuration:${Versions.jacodb}") - implementation("io.github.detekt.sarif4k", "sarif4k", Versions.sarif4k) + api("io.github.detekt.sarif4k", "sarif4k", Versions.sarif4k) api("io.github.microutils:kotlin-logging:${Versions.klogging}") } tasks.withType { kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf( - "-Xcontext-receivers", - ) + freeCompilerArgs += "-Xcontext-receivers" } } 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 7de539c8c9..0cd017325b 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 @@ -16,11 +16,6 @@ package org.usvm.dataflow.config -import org.usvm.dataflow.ifds.Maybe -import org.usvm.dataflow.ifds.onSome -import org.usvm.dataflow.taint.Tainted -import org.usvm.dataflow.util.Traits -import org.usvm.dataflow.util.removeTrailingElementAccessors import org.jacodb.api.common.CommonMethod import org.jacodb.api.common.cfg.CommonInst import org.jacodb.api.common.cfg.CommonValue @@ -40,6 +35,11 @@ import org.jacodb.taint.configuration.Or import org.jacodb.taint.configuration.PositionResolver import org.jacodb.taint.configuration.SourceFunctionMatches import org.jacodb.taint.configuration.TypeMatches +import org.usvm.dataflow.ifds.Maybe +import org.usvm.dataflow.ifds.onSome +import org.usvm.dataflow.taint.Tainted +import org.usvm.dataflow.util.Traits +import org.usvm.dataflow.util.removeTrailingElementAccessors context(Traits) open class BasicConditionEvaluator( @@ -76,35 +76,35 @@ open class BasicConditionEvaluator( override fun visit(condition: IsConstant): Boolean { positionResolver.resolve(condition.position).onSome { - return it.isConstant() + return isConstant(it) } return false } override fun visit(condition: ConstantEq): Boolean { positionResolver.resolve(condition.position).onSome { value -> - return value.eqConstant(condition.value) + return eqConstant(value, condition.value) } return false } override fun visit(condition: ConstantLt): Boolean { positionResolver.resolve(condition.position).onSome { value -> - return value.ltConstant(condition.value) + return ltConstant(value, condition.value) } return false } override fun visit(condition: ConstantGt): Boolean { positionResolver.resolve(condition.position).onSome { value -> - return value.gtConstant(condition.value) + return gtConstant(value, condition.value) } return false } override fun visit(condition: ConstantMatches): Boolean { positionResolver.resolve(condition.position).onSome { value -> - return value.matches(condition.pattern) + return matches(value, condition.pattern) } return false } @@ -119,7 +119,7 @@ open class BasicConditionEvaluator( override fun visit(condition: TypeMatches): Boolean { positionResolver.resolve(condition.position).onSome { value -> - return value.typeMatches(condition) + return typeMatches(value, condition) } return false } @@ -134,7 +134,7 @@ class FactAwareConditionEvaluator( override fun visit(condition: ContainsMark): Boolean { if (fact.mark != condition.mark) return false positionResolver.resolve(condition.position).onSome { value -> - val variable = value.toPath() + val variable = convertToPath(value) // FIXME: Adhoc for arrays val variableWithoutStars = variable.removeTrailingElementAccessors() 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 9386fd99c1..e85cb78cf9 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 @@ -16,14 +16,7 @@ package org.usvm.dataflow.config -import org.usvm.dataflow.ifds.AccessPath -import org.usvm.dataflow.ifds.ElementAccessor -import org.usvm.dataflow.ifds.Maybe -import org.usvm.dataflow.ifds.fmap -import org.usvm.dataflow.ifds.toMaybe -import org.usvm.dataflow.util.Traits import org.jacodb.api.common.CommonMethod -import org.jacodb.api.common.CommonProject import org.jacodb.api.common.cfg.CommonAssignInst import org.jacodb.api.common.cfg.CommonInst import org.jacodb.api.common.cfg.CommonInstanceCallExpr @@ -35,20 +28,26 @@ import org.jacodb.taint.configuration.PositionResolver import org.jacodb.taint.configuration.Result import org.jacodb.taint.configuration.ResultAnyElement import org.jacodb.taint.configuration.This +import org.usvm.dataflow.ifds.AccessPath +import org.usvm.dataflow.ifds.ElementAccessor +import org.usvm.dataflow.ifds.Maybe +import org.usvm.dataflow.ifds.fmap +import org.usvm.dataflow.ifds.toMaybe +import org.usvm.dataflow.util.Traits context(Traits) class CallPositionToAccessPathResolver( private val callStatement: CommonInst, ) : PositionResolver> { - private val callExpr = callStatement.getCallExpr() + private val callExpr = getCallExpr(callStatement) ?: error("Call statement should have non-null callExpr") override fun resolve(position: Position): Maybe = when (position) { AnyArgument -> Maybe.none() - is Argument -> callExpr.args[position.index].toPathOrNull().toMaybe() - This -> (callExpr as? CommonInstanceCallExpr)?.instance?.toPathOrNull().toMaybe() - Result -> (callStatement as? CommonAssignInst)?.lhv?.toPathOrNull().toMaybe() - ResultAnyElement -> (callStatement as? CommonAssignInst)?.lhv?.toPathOrNull().toMaybe() + 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 } } } @@ -57,7 +56,7 @@ context(Traits) class CallPositionToValueResolver( private val callStatement: CommonInst, ) : PositionResolver> { - private val callExpr = callStatement.getCallExpr() + private val callExpr = getCallExpr(callStatement) ?: error("Call statement should have non-null callExpr") override fun resolve(position: Position): Maybe = when (position) { @@ -72,14 +71,13 @@ class CallPositionToValueResolver( context(Traits) class EntryPointPositionToValueResolver( private val method: CommonMethod, - private val project: CommonProject, ) : PositionResolver> { override fun resolve(position: Position): Maybe = when (position) { - This -> Maybe.some(method.thisInstance) + This -> Maybe.some(getThisInstance(method)) is Argument -> { val p = method.parameters[position.index] - project.getArgument(p).toMaybe() + getArgument(p).toMaybe() } AnyArgument, Result, ResultAnyElement -> error("Unexpected $position") @@ -89,14 +87,13 @@ class EntryPointPositionToValueResolver( context(Traits) class EntryPointPositionToAccessPathResolver( private val method: CommonMethod, - private val project: CommonProject, ) : PositionResolver> { override fun resolve(position: Position): Maybe = when (position) { - This -> method.thisInstance.toPathOrNull().toMaybe() + This -> convertToPathOrNull(getThisInstance(method)).toMaybe() is Argument -> { val p = method.parameters[position.index] - project.getArgument(p)?.toPathOrNull().toMaybe() + getArgument(p)?.let { convertToPathOrNull(it) }.toMaybe() } AnyArgument, Result, ResultAnyElement -> error("Unexpected $position") diff --git a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/graph/BackwardGraphs.kt b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/graph/BackwardGraphs.kt index 8d4cbde899..5bf8b5e562 100644 --- a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/graph/BackwardGraphs.kt +++ b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/graph/BackwardGraphs.kt @@ -19,7 +19,6 @@ package org.usvm.dataflow.graph import org.jacodb.api.common.CommonMethod -import org.jacodb.api.common.CommonProject import org.jacodb.api.common.analysis.ApplicationGraph import org.jacodb.api.common.cfg.CommonInst @@ -29,9 +28,6 @@ private class BackwardApplicationGraphImpl( where Method : CommonMethod, Statement : CommonInst { - override val project: CommonProject - get() = forward.project - override fun predecessors(node: Statement) = forward.successors(node) override fun successors(node: Statement) = forward.predecessors(node) 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 2b7699394a..c5281712eb 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 @@ -144,7 +144,7 @@ class UniRunner( val (current, currentFact) = currentVertex val currentCallees = graph.callees(current).toList() - val currentIsCall = current.getCallExpr() != null + val currentIsCall = getCallExpr( current) != null val currentIsExit = current in graph.exitPoints(graph.methodOf(current)) if (currentIsCall) { 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 b49c1d6b89..f2b6727ac0 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 @@ -32,8 +32,8 @@ import io.github.detekt.sarif4k.ThreadFlowLocation import io.github.detekt.sarif4k.Tool import io.github.detekt.sarif4k.ToolComponent import io.github.detekt.sarif4k.Version -import org.usvm.dataflow.ifds.Vertex import org.jacodb.api.common.cfg.CommonInst +import org.usvm.dataflow.ifds.Vertex import org.usvm.dataflow.util.Traits private const val SARIF_SCHEMA = @@ -99,12 +99,12 @@ private fun instToSarifLocation( uri = sourceLocation ), region = Region( - startLine = inst.lineNumber()?.toLong() + startLine = lineNumber(inst).toLong() ) ), logicalLocations = listOf( LogicalLocation( - fullyQualifiedName = inst.locationFQN() + fullyQualifiedName = locationFQN(inst) ) ) ) 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 439980e952..30cf52fefe 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 @@ -22,6 +22,8 @@ import org.jacodb.api.common.analysis.ApplicationGraph import org.jacodb.api.common.cfg.CommonInst import org.jacodb.taint.configuration.TaintConfigurationItem import org.jacodb.taint.configuration.TaintMethodSink +import org.usvm.dataflow.config.CallPositionToValueResolver +import org.usvm.dataflow.config.FactAwareConditionEvaluator import org.usvm.dataflow.ifds.Analyzer import org.usvm.dataflow.ifds.Edge import org.usvm.dataflow.ifds.Reason @@ -53,9 +55,9 @@ class TaintAnalyzer( } run { - val callExpr = edge.to.statement.getCallExpr() ?: return@run + val callExpr = getCallExpr(edge.to.statement) ?: return@run - val callee = callExpr.callee + val callee = getCallee(callExpr) val config = getConfigForMethod(callee) ?: return@run @@ -66,9 +68,9 @@ class TaintAnalyzer( } // Determine whether 'edge.to' is a sink via config: - val conditionEvaluator = org.usvm.dataflow.config.FactAwareConditionEvaluator( + val conditionEvaluator = FactAwareConditionEvaluator( edge.to.fact, - org.usvm.dataflow.config.CallPositionToValueResolver(edge.to.statement), + CallPositionToValueResolver(edge.to.statement), ) for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { @@ -86,11 +88,11 @@ class TaintAnalyzer( val statement = edge.to.statement val fact = edge.to.fact if (fact is Tainted && fact.mark.name == "UNTRUSTED") { - val branchExprCondition = statement.getBranchExprCondition() - if (branchExprCondition != null && statement.isLoopHead()) { - val conditionOperands = branchExprCondition.getValues() + val branchExprCondition = getBranchExprCondition(statement) + if (branchExprCondition != null && isLoopHead(statement)) { + val conditionOperands = getValues(branchExprCondition) for (s in conditionOperands) { - val p = s.toPath() + val p = convertToPath(s) if (p == fact.variable) { val message = "Untrusted loop bound" val vulnerability = TaintVulnerability(message, sink = edge.to) @@ -104,10 +106,10 @@ class TaintAnalyzer( val statement = edge.to.statement val fact = edge.to.fact if (fact is Tainted && fact.mark.name == "UNTRUSTED") { - val arrayAllocation = statement.getArrayAllocation() + val arrayAllocation = getArrayAllocation(statement) if (arrayAllocation != null) { - for (arg in arrayAllocation.getValues()) { - if (arg.toPath() == fact.variable) { + 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)) @@ -120,9 +122,9 @@ class TaintAnalyzer( val statement = edge.to.statement val fact = edge.to.fact if (fact is Tainted && fact.mark.name == "UNTRUSTED") { - val arrayAccessIndex = statement.getArrayAccessIndex() + val arrayAccessIndex = getArrayAccessIndex(statement) if (arrayAccessIndex != null) { - if (arrayAccessIndex.toPath() == fact.variable) { + if (convertToPath(arrayAccessIndex) == fact.variable) { val message = "Untrusted index for access array" val vulnerability = TaintVulnerability(message, sink = edge.to) add(NewVulnerability(vulnerability)) 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 3f871b5a16..2d6d02e7aa 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 @@ -36,6 +36,13 @@ import org.jacodb.taint.configuration.TaintConfigurationItem import org.jacodb.taint.configuration.TaintEntryPointSource import org.jacodb.taint.configuration.TaintMethodSource import org.jacodb.taint.configuration.TaintPassThrough +import org.usvm.dataflow.config.BasicConditionEvaluator +import org.usvm.dataflow.config.CallPositionToAccessPathResolver +import org.usvm.dataflow.config.CallPositionToValueResolver +import org.usvm.dataflow.config.EntryPointPositionToAccessPathResolver +import org.usvm.dataflow.config.EntryPointPositionToValueResolver +import org.usvm.dataflow.config.FactAwareConditionEvaluator +import org.usvm.dataflow.config.TaintActionEvaluator import org.usvm.dataflow.ifds.ElementAccessor import org.usvm.dataflow.ifds.FlowFunction import org.usvm.dataflow.ifds.FlowFunctions @@ -56,9 +63,6 @@ class ForwardTaintFlowFunctions( where Method : CommonMethod, Statement : CommonInst { - private val cp: CommonProject - get() = graph.project - override fun obtainPossibleStartFacts( method: Method, ): Collection = buildSet { @@ -68,11 +72,11 @@ class ForwardTaintFlowFunctions( // Extract initial facts from the config: val config = getConfigForMethod(method) if (config != null) { - val conditionEvaluator = org.usvm.dataflow.config.BasicConditionEvaluator( - org.usvm.dataflow.config.EntryPointPositionToValueResolver(method, cp) + val conditionEvaluator = BasicConditionEvaluator( + EntryPointPositionToValueResolver(method) ) - val actionEvaluator = org.usvm.dataflow.config.TaintActionEvaluator( - org.usvm.dataflow.config.EntryPointPositionToAccessPathResolver(method, cp) + val actionEvaluator = TaintActionEvaluator( + EntryPointPositionToAccessPathResolver(method) ) // Handle EntryPointSource config items: @@ -95,8 +99,8 @@ class ForwardTaintFlowFunctions( from: CommonExpr, to: CommonValue, ): Collection { - val toPath = to.toPath() - val fromPath = from.toPathOrNull() + val toPath = convertToPath(to) + val fromPath = convertToPathOrNull(from) if (fromPath != null) { // Adhoc taint array: @@ -143,8 +147,8 @@ class ForwardTaintFlowFunctions( check(fact is Tainted) if (current is CommonAssignInst) { - current.taintFlowRhsValues().flatMap { rhvValue -> - transmitTaintAssign(fact, from = rhvValue, to = current.lhv) + taintFlowRhsValues(current).flatMap { rhv -> + transmitTaintAssign(fact, from = rhv, to = current.lhv) } } else { transmitTaintNormal(fact) @@ -156,8 +160,8 @@ class ForwardTaintFlowFunctions( from: CommonValue, to: CommonValue, ): Collection = buildSet { - val fromPath = from.toPath() - val toPath = to.toPath() + val fromPath = convertToPath(from) + val toPath = convertToPath(to) val tail = (fact.variable - fromPath) ?: return@buildSet val newPath = toPath + tail @@ -199,10 +203,10 @@ class ForwardTaintFlowFunctions( callStatement: Statement, returnSite: Statement, // FIXME: unused? ) = FlowFunction { fact -> - val callExpr = callStatement.getCallExpr() + val callExpr = getCallExpr(callStatement) ?: error("Call statement should have non-null callExpr") - val callee = callExpr.callee + val callee = getCallee(callExpr) val config = getConfigForMethod(callee) if (fact == TaintZeroFact) { @@ -210,11 +214,11 @@ class ForwardTaintFlowFunctions( add(TaintZeroFact) if (config != null) { - val conditionEvaluator = org.usvm.dataflow.config.BasicConditionEvaluator( - org.usvm.dataflow.config.CallPositionToValueResolver(callStatement) + val conditionEvaluator = BasicConditionEvaluator( + CallPositionToValueResolver(callStatement) ) - val actionEvaluator = org.usvm.dataflow.config.TaintActionEvaluator( - org.usvm.dataflow.config.CallPositionToAccessPathResolver(callStatement) + val actionEvaluator = TaintActionEvaluator( + CallPositionToAccessPathResolver(callStatement) ) // Handle MethodSource config items: @@ -234,13 +238,13 @@ class ForwardTaintFlowFunctions( } check(fact is Tainted) - val statementPassThrough = callStatement.taintPassThrough() + val statementPassThrough = taintPassThrough(callStatement) if (statementPassThrough != null) { for ((from, to) in statementPassThrough) { - if (from.toPath() == fact.variable) { + if (convertToPath(from) == fact.variable) { return@FlowFunction setOf( fact, - fact.copy(variable = to.toPath()) + fact.copy(variable = convertToPath(to)) ) } } @@ -250,11 +254,11 @@ class ForwardTaintFlowFunctions( if (config != null) { val facts = mutableSetOf() - val conditionEvaluator = org.usvm.dataflow.config.FactAwareConditionEvaluator( - fact, org.usvm.dataflow.config.CallPositionToValueResolver(callStatement) + val conditionEvaluator = FactAwareConditionEvaluator( + fact, CallPositionToValueResolver(callStatement) ) - val actionEvaluator = org.usvm.dataflow.config.TaintActionEvaluator( - org.usvm.dataflow.config.CallPositionToAccessPathResolver(callStatement) + val actionEvaluator = TaintActionEvaluator( + CallPositionToAccessPathResolver(callStatement) ) var defaultBehavior = true @@ -305,7 +309,7 @@ class ForwardTaintFlowFunctions( } // FIXME: adhoc for constructors: - if (callee.isConstructor) { + if (isConstructor(callee)) { return@FlowFunction listOf(fact) } @@ -326,14 +330,14 @@ class ForwardTaintFlowFunctions( for (actual in callExpr.args) { // Possibly tainted actual parameter: - if (fact.variable.startsWith(actual.toPathOrNull())) { + 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(callExpr.instance.toPathOrNull())) { + if (fact.variable.startsWith(convertToPathOrNull(callExpr.instance))) { return@FlowFunction emptyList() // Will be handled by summary edge } } @@ -342,7 +346,7 @@ class ForwardTaintFlowFunctions( if (callStatement is CommonAssignInst) { // Possibly tainted lhv: - if (fact.variable.startsWith(callStatement.lhv.toPathOrNull())) { + if (fact.variable.startsWith(convertToPathOrNull(callStatement.lhv))) { return@FlowFunction emptyList() // Overridden by rhv } } @@ -362,13 +366,13 @@ class ForwardTaintFlowFunctions( } check(fact is Tainted) - val callExpr = callStatement.getCallExpr() + 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 = cp.getArgumentsOf(callee) + val formalParams = getArgumentsOf(callee) for ((formal, actual) in formalParams.zip(actualParams)) { addAll(transmitTaintArgumentActualToFormal(fact, from = actual, to = formal)) } @@ -379,7 +383,7 @@ class ForwardTaintFlowFunctions( transmitTaintInstanceToThis( fact = fact, from = callExpr.instance, - to = callee.thisInstance + to = getThisInstance(callee), ) ) } @@ -401,7 +405,7 @@ class ForwardTaintFlowFunctions( } check(fact is Tainted) - val callExpr = callStatement.getCallExpr() + val callExpr = getCallExpr(callStatement) ?: error("Call statement should have non-null callExpr") val callee = graph.methodOf(exitStatement) @@ -409,13 +413,13 @@ class ForwardTaintFlowFunctions( // 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 = cp.getArgumentsOf(callee) + val formalParams = getArgumentsOf(callee) for ((formal, actual) in formalParams.zip(actualParams)) { addAll( transmitTaintArgumentFormalToActual( fact = fact, from = formal, - to = actual + to = actual, ) ) } @@ -426,8 +430,8 @@ class ForwardTaintFlowFunctions( addAll( transmitTaintThisToInstance( fact = fact, - from = callee.thisInstance, - to = callExpr.instance + from = getThisInstance(callee), + to = callExpr.instance, ) ) } @@ -448,16 +452,13 @@ class ForwardTaintFlowFunctions( } } -context(Traits) +context(Traits) class BackwardTaintFlowFunctions( private val graph: ApplicationGraph, ) : FlowFunctions where Method : CommonMethod, Statement : CommonInst { - private val cp: CommonProject - get() = graph.project - override fun obtainPossibleStartFacts( method: Method, ): Collection { @@ -469,8 +470,8 @@ class BackwardTaintFlowFunctions( from: CommonValue, to: CommonExpr, ): Collection { - val fromPath = from.toPath() - val toPath = to.toPathOrNull() + val fromPath = convertToPath(from) + val toPath = convertToPathOrNull(to) if (toPath != null) { val tail = fact.variable - fromPath @@ -519,8 +520,8 @@ class BackwardTaintFlowFunctions( from: CommonValue, to: CommonValue, ): Collection = buildSet { - val fromPath = from.toPath() - val toPath = to.toPath() + val fromPath = convertToPath(from) + val toPath = convertToPath(to) val tail = (fact.variable - fromPath) ?: return@buildSet val newPath = toPath + tail @@ -569,9 +570,9 @@ class BackwardTaintFlowFunctions( } check(fact is Tainted) - val callExpr = callStatement.getCallExpr() + val callExpr = getCallExpr(callStatement) ?: error("Call statement should have non-null callExpr") - val callee = callExpr.callee + val callee = getCallee( callExpr) if (callee in graph.callees(callStatement)) { @@ -581,14 +582,14 @@ class BackwardTaintFlowFunctions( for (actual in callExpr.args) { // Possibly tainted actual parameter: - if (fact.variable.startsWith(actual.toPathOrNull())) { + 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(callExpr.instance.toPathOrNull())) { + if (fact.variable.startsWith(convertToPathOrNull( callExpr.instance))) { return@FlowFunction emptyList() // Will be handled by summary edge } } @@ -597,7 +598,7 @@ class BackwardTaintFlowFunctions( if (callStatement is CommonAssignInst) { // Possibly tainted rhv: - if (fact.variable.startsWith(callStatement.rhv.toPathOrNull())) { + if (fact.variable.startsWith(convertToPathOrNull( callStatement.rhv))) { return@FlowFunction emptyList() // Overridden by lhv } } @@ -617,13 +618,13 @@ class BackwardTaintFlowFunctions( } check(fact is Tainted) - val callExpr = callStatement.getCallExpr() + 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 = cp.getArgumentsOf(callee) + val formalParams = getArgumentsOf(callee) for ((formal, actual) in formalParams.zip(actualParams)) { addAll(transmitTaintArgumentActualToFormal(fact, from = actual, to = formal)) } @@ -634,7 +635,7 @@ class BackwardTaintFlowFunctions( transmitTaintInstanceToThis( fact = fact, from = callExpr.instance, - to = callee.thisInstance + to = getThisInstance(callee), ) ) } @@ -652,7 +653,7 @@ class BackwardTaintFlowFunctions( transmitTaintReturn( fact = fact, from = callStatement.lhv, - to = returnValue + to = returnValue, ) ) } @@ -670,7 +671,7 @@ class BackwardTaintFlowFunctions( } check(fact is Tainted) - val callExpr = callStatement.getCallExpr() + val callExpr = getCallExpr(callStatement) ?: error("Call statement should have non-null callExpr") val callee = graph.methodOf(exitStatement) @@ -678,13 +679,13 @@ class BackwardTaintFlowFunctions( // 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 = cp.getArgumentsOf(callee) + val formalParams = getArgumentsOf(callee) for ((formal, actual) in formalParams.zip(actualParams)) { addAll( transmitTaintArgumentFormalToActual( fact = fact, from = formal, - to = actual + to = actual, ) ) } @@ -695,8 +696,8 @@ class BackwardTaintFlowFunctions( addAll( transmitTaintThisToInstance( fact = fact, - from = callee.thisInstance, - to = callExpr.instance + from = getThisInstance(callee), + to = callExpr.instance, ) ) } diff --git a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/util/Traits.kt b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/util/Traits.kt index 7f2cc0f151..a2aff85d40 100644 --- a/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/util/Traits.kt +++ b/usvm-dataflow/src/main/kotlin/org/usvm/dataflow/util/Traits.kt @@ -18,7 +18,6 @@ package org.usvm.dataflow.util import org.jacodb.api.common.CommonMethod import org.jacodb.api.common.CommonMethodParameter -import org.jacodb.api.common.CommonProject import org.jacodb.api.common.cfg.CommonArgument import org.jacodb.api.common.cfg.CommonAssignInst import org.jacodb.api.common.cfg.CommonCallExpr @@ -37,43 +36,32 @@ interface Traits where Method : CommonMethod, Statement : CommonInst { - val @UnsafeVariance Method.thisInstance: CommonThis - val @UnsafeVariance Method.isConstructor: Boolean - - fun CommonExpr.toPathOrNull(): AccessPath? - fun CommonValue.toPathOrNull(): AccessPath? - fun CommonValue.toPath(): AccessPath - - val CommonCallExpr.callee: Method - - fun CommonProject.getArgument(param: CommonMethodParameter): CommonArgument? - fun CommonProject.getArgumentsOf(method: @UnsafeVariance Method): List - - fun CommonValue.isConstant(): Boolean - fun CommonValue.eqConstant(constant: ConstantValue): Boolean - fun CommonValue.ltConstant(constant: ConstantValue): Boolean - fun CommonValue.gtConstant(constant: ConstantValue): Boolean - fun CommonValue.matches(pattern: String): Boolean - - // TODO: remove - fun CommonExpr.toPaths(): List = listOfNotNull(toPathOrNull()) - - fun @UnsafeVariance Statement.getCallExpr(): CommonCallExpr? - fun CommonExpr.getValues(): Set - fun @UnsafeVariance Statement.getOperands(): List - fun @UnsafeVariance Statement.getBranchExprCondition(): CommonExpr? - - fun @UnsafeVariance Statement.getArrayAllocation(): CommonExpr? - fun @UnsafeVariance Statement.getArrayAccessIndex(): CommonValue? - - fun @UnsafeVariance Statement.isLoopHead(): Boolean - - fun @UnsafeVariance Statement.lineNumber(): Int? - fun @UnsafeVariance Statement.locationFQN(): String? - - fun CommonValue.typeMatches(condition: TypeMatches): Boolean - - fun CommonAssignInst.taintFlowRhsValues(): List - - fun @UnsafeVariance Statement.taintPassThrough(): List>? + fun convertToPathOrNull(expr: CommonExpr): AccessPath? + fun convertToPathOrNull(value: CommonValue): AccessPath? + fun convertToPath(value: CommonValue): AccessPath + + fun getThisInstance(method: @UnsafeVariance Method): CommonThis + fun getArgument(param: CommonMethodParameter): CommonArgument? + fun getArgumentsOf(method: @UnsafeVariance Method): List + fun getCallee(callExpr: CommonCallExpr): Method + fun getCallExpr(statement: @UnsafeVariance Statement): CommonCallExpr? + fun getValues(expr: CommonExpr): Set + fun getOperands(statement: @UnsafeVariance Statement): List + fun getArrayAllocation(statement: @UnsafeVariance Statement): CommonExpr? + fun getArrayAccessIndex(statement: @UnsafeVariance Statement): CommonValue? + fun getBranchExprCondition(statement: @UnsafeVariance Statement): CommonExpr? + + fun isConstant(value: CommonValue): Boolean + fun eqConstant(value: CommonValue, constant: ConstantValue): Boolean + fun ltConstant(value: CommonValue, constant: ConstantValue): Boolean + fun gtConstant(value: CommonValue, constant: ConstantValue): Boolean + fun matches(value: CommonValue, pattern: String): Boolean + fun typeMatches(value: CommonValue, condition: TypeMatches): Boolean + + fun isConstructor(method: @UnsafeVariance Method): Boolean + fun isLoopHead(statement: @UnsafeVariance Statement): Boolean + fun lineNumber(statement: @UnsafeVariance Statement): Int + fun locationFQN(statement: @UnsafeVariance Statement): String + fun taintFlowRhsValues(statement: CommonAssignInst): List + fun taintPassThrough(statement: @UnsafeVariance Statement): List>? } diff --git a/usvm-jvm-dataflow/build.gradle.kts b/usvm-jvm-dataflow/build.gradle.kts index 4cec4708da..8a8156964b 100644 --- a/usvm-jvm-dataflow/build.gradle.kts +++ b/usvm-jvm-dataflow/build.gradle.kts @@ -36,9 +36,7 @@ dependencies { tasks.withType { kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf( - "-Xcontext-receivers", - ) + freeCompilerArgs += "-Xcontext-receivers" } } diff --git a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/graph/JcApplicationGraphImpl.kt b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/graph/JcApplicationGraphImpl.kt index 4bde80e80f..a24dc62347 100644 --- a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/graph/JcApplicationGraphImpl.kt +++ b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/graph/JcApplicationGraphImpl.kt @@ -27,7 +27,7 @@ import org.jacodb.impl.features.SyncUsagesExtension * Possible we will need JcRawInst instead of JcInst */ open class JcApplicationGraphImpl( - override val project: JcClasspath, + override val cp: JcClasspath, private val usages: SyncUsagesExtension, ) : JcApplicationGraph { override fun predecessors(node: JcInst): Sequence { diff --git a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/graph/SimplifiedJcApplicationGraph.kt b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/graph/SimplifiedJcApplicationGraph.kt index 9b28bc4e52..f2d412258d 100644 --- a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/graph/SimplifiedJcApplicationGraph.kt +++ b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/graph/SimplifiedJcApplicationGraph.kt @@ -39,7 +39,7 @@ internal class SimplifiedJcApplicationGraph( private val bannedPackagePrefixes: List, ) : JcApplicationGraph by graph { private val hierarchyExtension = runBlocking { - project.hierarchyExt() + cp.hierarchyExt() } private val visitedCallers: MutableMap> = mutableMapOf() 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 f40e01f282..7ab623e0ce 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 @@ -16,12 +16,14 @@ package org.usvm.dataflow.jvm.npe -import org.jacodb.api.common.analysis.ApplicationGraph import org.jacodb.api.jvm.JcMethod +import org.jacodb.api.jvm.analysis.JcApplicationGraph import org.jacodb.api.jvm.cfg.JcInst import org.jacodb.taint.configuration.TaintConfigurationItem import org.jacodb.taint.configuration.TaintMark import org.jacodb.taint.configuration.TaintMethodSink +import org.usvm.dataflow.config.CallPositionToValueResolver +import org.usvm.dataflow.config.FactAwareConditionEvaluator import org.usvm.dataflow.ifds.Analyzer import org.usvm.dataflow.ifds.Reason import org.usvm.dataflow.jvm.util.JcTraits @@ -39,8 +41,8 @@ private val logger = mu.KotlinLogging.logger {} context(JcTraits) class NpeAnalyzer( - private val graph: ApplicationGraph, - private val getConfigForMethod: (JcMethod) -> List? + private val graph: JcApplicationGraph, + private val getConfigForMethod: (JcMethod) -> List?, ) : Analyzer, JcMethod, JcInst> { override val flowFunctions: ForwardNpeFlowFunctions by lazy { @@ -73,10 +75,10 @@ class NpeAnalyzer( } run { - val callExpr = edge.to.statement.getCallExpr() ?: return@run - val callee = callExpr.callee + 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. @@ -85,9 +87,9 @@ class NpeAnalyzer( } // Determine whether 'edge.to' is a sink via config: - val conditionEvaluator = org.usvm.dataflow.config.FactAwareConditionEvaluator( + val conditionEvaluator = FactAwareConditionEvaluator( edgeToFact, - org.usvm.dataflow.config.CallPositionToValueResolver(edge.to.statement), + CallPositionToValueResolver(edge.to.statement), ) for (item in config.filterIsInstance()) { if (item.condition.accept(conditionEvaluator)) { 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 598d9a1cd6..4fc99a1eb0 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 @@ -16,20 +16,18 @@ package org.usvm.dataflow.jvm.npe -import org.jacodb.api.common.CommonProject -import org.jacodb.api.common.analysis.ApplicationGraph import org.jacodb.api.common.cfg.CommonAssignInst import org.jacodb.api.common.cfg.CommonExpr -import org.jacodb.api.common.cfg.CommonThis import org.jacodb.api.common.cfg.CommonValue import org.jacodb.api.jvm.JcArrayType import org.jacodb.api.jvm.JcClasspath import org.jacodb.api.jvm.JcMethod +import org.jacodb.api.jvm.analysis.JcApplicationGraph import org.jacodb.api.jvm.cfg.JcArgument import org.jacodb.api.jvm.cfg.JcAssignInst import org.jacodb.api.jvm.cfg.JcCallExpr -import org.jacodb.api.jvm.cfg.JcDynamicCallExpr import org.jacodb.api.jvm.cfg.JcEqExpr +import org.jacodb.api.jvm.cfg.JcExpr import org.jacodb.api.jvm.cfg.JcIfInst import org.jacodb.api.jvm.cfg.JcInst import org.jacodb.api.jvm.cfg.JcInstanceCallExpr @@ -37,6 +35,8 @@ import org.jacodb.api.jvm.cfg.JcNeqExpr import org.jacodb.api.jvm.cfg.JcNewArrayExpr import org.jacodb.api.jvm.cfg.JcNullConstant import org.jacodb.api.jvm.cfg.JcReturnInst +import org.jacodb.api.jvm.cfg.JcThis +import org.jacodb.api.jvm.cfg.JcValue import org.jacodb.api.jvm.ext.findType import org.jacodb.api.jvm.ext.isNullable import org.jacodb.taint.configuration.AssignMark @@ -55,6 +55,7 @@ import org.usvm.dataflow.config.CallPositionToAccessPathResolver import org.usvm.dataflow.config.CallPositionToValueResolver import org.usvm.dataflow.config.EntryPointPositionToAccessPathResolver import org.usvm.dataflow.config.EntryPointPositionToValueResolver +import org.usvm.dataflow.config.FactAwareConditionEvaluator import org.usvm.dataflow.config.TaintActionEvaluator import org.usvm.dataflow.ifds.AccessPath import org.usvm.dataflow.ifds.ElementAccessor @@ -74,12 +75,12 @@ private val logger = mu.KotlinLogging.logger {} context(JcTraits) class ForwardNpeFlowFunctions( - private val graph: ApplicationGraph, - private val getConfigForMethod: (JcMethod) -> List? + private val graph: JcApplicationGraph, + private val getConfigForMethod: (JcMethod) -> List?, ) : FlowFunctions { - private val cp: CommonProject - get() = graph.project + private val cp: JcClasspath + get() = graph.cp override fun obtainPossibleStartFacts( method: JcMethod, @@ -88,9 +89,9 @@ class ForwardNpeFlowFunctions( // Possibly null arguments: for (p in method.parameters.filter { it.isNullable != false }) { - val t = (cp as JcClasspath).findType(p.type.typeName) + val t = cp.findType(p.type.typeName) val arg = JcArgument.of(p.index, p.name, t) - val path = arg.toPath() + val path = convertToPath(arg) add(Tainted(path, TaintMark.NULLNESS)) } } @@ -106,10 +107,10 @@ class ForwardNpeFlowFunctions( if (config != null) { val conditionEvaluator = BasicConditionEvaluator( - EntryPointPositionToValueResolver(method, cp) + EntryPointPositionToValueResolver(method) ) val actionEvaluator = TaintActionEvaluator( - EntryPointPositionToAccessPathResolver(method, cp) + EntryPointPositionToAccessPathResolver(method) ) // Handle EntryPointSource config items: @@ -132,8 +133,11 @@ class ForwardNpeFlowFunctions( from: CommonExpr, to: CommonValue, ): Collection { - val toPath = to.toPath() - val fromPath = from.toPathOrNull() + from as JcExpr + to as JcValue + + val toPath = convertToPath(to) + val fromPath = convertToPathOrNull(from) if (fact.mark == TaintMark.NULLNESS) { // TODO: consider @@ -194,7 +198,7 @@ class ForwardNpeFlowFunctions( inst: JcInst, ): Collection = buildList { if (inst is CommonAssignInst) { - val toPath = inst.lhv.toPath() + 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)) @@ -210,9 +214,9 @@ class ForwardNpeFlowFunctions( get() { val expr = condition return if (expr.rhv is JcNullConstant) { - expr.lhv.toPathOrNull() + convertToPathOrNull(expr.lhv) } else if (expr.lhv is JcNullConstant) { - expr.rhv.toPathOrNull() + convertToPathOrNull(expr.rhv) } else { null } @@ -271,8 +275,8 @@ class ForwardNpeFlowFunctions( private fun transmitTaint( fact: Tainted, at: JcInst, - from: CommonValue, - to: CommonValue, + from: JcValue, + to: JcValue, ): Collection = buildSet { if (fact.mark == TaintMark.NULLNESS) { if (fact.variable.isDereferencedAt(at)) { @@ -280,8 +284,8 @@ class ForwardNpeFlowFunctions( } } - val fromPath = from.toPath() - val toPath = to.toPath() + val fromPath = convertToPath(from) + val toPath = convertToPath(to) val tail = (fact.variable - fromPath) ?: return@buildSet val newPath = toPath + tail @@ -292,36 +296,36 @@ class ForwardNpeFlowFunctions( private fun transmitTaintArgumentActualToFormal( fact: Tainted, at: JcInst, - from: CommonValue, // actual - to: CommonValue, // formal + from: JcValue, // actual + to: JcValue, // formal ): Collection = transmitTaint(fact, at, from, to) private fun transmitTaintArgumentFormalToActual( fact: Tainted, at: JcInst, - from: CommonValue, // formal - to: CommonValue, // actual + from: JcValue, // formal + to: JcValue, // actual ): Collection = transmitTaint(fact, at, from, to) private fun transmitTaintInstanceToThis( fact: Tainted, at: JcInst, - from: CommonValue, // instance - to: CommonThis, // this + from: JcValue, // instance + to: JcThis, // this ): Collection = transmitTaint(fact, at, from, to) private fun transmitTaintThisToInstance( fact: Tainted, at: JcInst, - from: CommonThis, // this - to: CommonValue, // instance + from: JcThis, // this + to: JcValue, // instance ): Collection = transmitTaint(fact, at, from, to) private fun transmitTaintReturn( fact: Tainted, at: JcInst, - from: CommonValue, - to: CommonValue, + from: JcValue, + to: JcValue, ): Collection = transmitTaint(fact, at, from, to) override fun obtainCallToReturnSiteFlowFunction( @@ -334,10 +338,10 @@ class ForwardNpeFlowFunctions( } } - val callExpr = callStatement.getCallExpr() + val callExpr = getCallExpr(callStatement) ?: error("Call statement should have non-null callExpr") - val callee = callExpr.callee + val callee = getCallee(callExpr) val config = getConfigForMethod(callee) if (fact == TaintZeroFact) { @@ -345,7 +349,7 @@ class ForwardNpeFlowFunctions( add(TaintZeroFact) if (callStatement is JcAssignInst) { - val toPath = callStatement.lhv.toPath() + 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)) @@ -384,13 +388,13 @@ class ForwardNpeFlowFunctions( } check(fact is Tainted) - val statementPassThrough = callStatement.taintPassThrough() + val statementPassThrough = taintPassThrough(callStatement) if (statementPassThrough != null) { for ((from, to) in statementPassThrough) { - if (from.toPath() == fact.variable) { + if (convertToPath(from) == fact.variable) { return@FlowFunction setOf( fact, - fact.copy(variable = to.toPath()) + fact.copy(variable = convertToPath(to)) ) } } @@ -404,8 +408,9 @@ class ForwardNpeFlowFunctions( // Skip rules for StringBuilder::append in NPE analysis. } else { val facts = mutableSetOf() - val conditionEvaluator = org.usvm.dataflow.config.FactAwareConditionEvaluator( - fact, org.usvm.dataflow.config.CallPositionToValueResolver(callStatement) + val conditionEvaluator = FactAwareConditionEvaluator( + fact, + CallPositionToValueResolver(callStatement) ) val actionEvaluator = TaintActionEvaluator( CallPositionToAccessPathResolver(callStatement) @@ -481,14 +486,16 @@ class ForwardNpeFlowFunctions( for (actual in callExpr.args) { // Possibly tainted actual parameter: - if (fact.variable.startsWith(actual.toPathOrNull())) { + 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: - if (fact.variable.startsWith(callExpr.instance.toPathOrNull())) { + val p = convertToPathOrNull(callExpr.instance) + if (fact.variable.startsWith(p)) { return@FlowFunction emptyList() // Will be handled by summary edge } } @@ -497,7 +504,8 @@ class ForwardNpeFlowFunctions( if (callStatement is JcAssignInst) { // Possibly tainted lhv: - if (fact.variable.startsWith(callStatement.lhv.toPathOrNull())) { + val p = convertToPathOrNull(callStatement.lhv) + if (fact.variable.startsWith(p)) { return@FlowFunction emptyList() // Overridden by rhv } } @@ -517,13 +525,13 @@ class ForwardNpeFlowFunctions( } check(fact is Tainted) - val callExpr = callStatement.getCallExpr() + 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 = cp.getArgumentsOf(callee) + val formalParams = getArgumentsOf(callee) for ((formal, actual) in formalParams.zip(actualParams)) { addAll( transmitTaintArgumentActualToFormal( @@ -542,7 +550,7 @@ class ForwardNpeFlowFunctions( fact = fact, at = callStatement, from = callExpr.instance, - to = callee.thisInstance + to = getThisInstance(callee) ) ) } @@ -568,7 +576,7 @@ class ForwardNpeFlowFunctions( // Note: returnValue can be null here in some weird cases, e.g. in lambda. exitStatement.returnValue?.let { returnValue -> if (returnValue is JcNullConstant) { - val toPath = callStatement.lhv.toPath() + val toPath = convertToPath(callStatement.lhv) add(Tainted(toPath, TaintMark.NULLNESS)) } } @@ -577,7 +585,7 @@ class ForwardNpeFlowFunctions( } check(fact is Tainted) - val callExpr = callStatement.getCallExpr() + val callExpr = getCallExpr(callStatement) ?: error("Call statement should have non-null callExpr") val callee = graph.methodOf(exitStatement) @@ -585,7 +593,7 @@ class ForwardNpeFlowFunctions( // 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 = cp.getArgumentsOf(callee) + val formalParams = getArgumentsOf(callee) for ((formal, actual) in formalParams.zip(actualParams)) { addAll( transmitTaintArgumentFormalToActual( @@ -604,8 +612,8 @@ class ForwardNpeFlowFunctions( transmitTaintThisToInstance( fact = fact, at = callStatement, - from = callee.thisInstance, - to = callExpr.instance + from = getThisInstance(callee), + to = callExpr.instance, ) ) } @@ -624,7 +632,7 @@ class ForwardNpeFlowFunctions( fact = fact, at = callStatement, from = returnValue, - to = callStatement.lhv + 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 eb59bb3bab..f9c65c001e 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 @@ -16,9 +16,8 @@ package org.usvm.dataflow.jvm.npe -import org.jacodb.api.common.analysis.ApplicationGraph -import org.jacodb.api.jvm.JcClasspath import org.jacodb.api.jvm.JcMethod +import org.jacodb.api.jvm.analysis.JcApplicationGraph import org.jacodb.api.jvm.cfg.JcInst import org.jacodb.taint.configuration.TaintConfigurationFeature import org.jacodb.taint.configuration.TaintConfigurationItem @@ -36,16 +35,17 @@ private val logger = mu.KotlinLogging.logger {} context(JcTraits) class NpeManager( - graph: ApplicationGraph, + graph: JcApplicationGraph, unitResolver: UnitResolver, private val getConfigForMethod: (JcMethod) -> List?, ) : TaintManager(graph, unitResolver, useBidiRunner = false, getConfigForMethod) { + override fun newRunner( unit: UnitType, ): TaintRunner { check(unit !in runnerForUnit) { "Runner for $unit already exists" } - val analyzer = NpeAnalyzer(graph, getConfigForMethod) + val analyzer = NpeAnalyzer(graph as JcApplicationGraph, getConfigForMethod) val runner = UniRunner( graph = graph, analyzer = analyzer, @@ -69,12 +69,12 @@ class NpeManager( } fun jcNpeManager( - graph: ApplicationGraph, + graph: JcApplicationGraph, unitResolver: JcUnitResolver, - getConfigForMethod: ((JcMethod) -> List?)? = null -): NpeManager = with(JcTraits) { + getConfigForMethod: ((JcMethod) -> List?)? = null, +): NpeManager = with(JcTraits(graph.cp)) { val config: (JcMethod) -> List? = getConfigForMethod ?: run { - val taintConfigurationFeature = (graph.project as JcClasspath).features + val taintConfigurationFeature = cp.features ?.singleOrNull { it is TaintConfigurationFeature } ?.let { it as TaintConfigurationFeature } 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 5f0ba498c6..7c89966eef 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 @@ -16,44 +16,43 @@ package org.usvm.dataflow.jvm.npe +import org.jacodb.api.jvm.cfg.JcExpr +import org.jacodb.api.jvm.cfg.JcInst +import org.jacodb.api.jvm.cfg.JcInstanceCallExpr +import org.jacodb.api.jvm.cfg.JcLengthExpr +import org.jacodb.api.jvm.cfg.values import org.usvm.dataflow.ifds.AccessPath import org.usvm.dataflow.ifds.minus -import org.usvm.dataflow.util.Traits +import org.usvm.dataflow.jvm.util.JcTraits import org.usvm.dataflow.util.startsWith -import org.jacodb.api.common.CommonMethod -import org.jacodb.api.common.cfg.CommonExpr -import org.jacodb.api.common.cfg.CommonInst -import org.jacodb.api.jvm.cfg.JcInstanceCallExpr -import org.jacodb.api.jvm.cfg.JcLengthExpr -context(Traits) -internal fun AccessPath?.isDereferencedAt(expr: CommonExpr): Boolean { +context(JcTraits) +internal fun AccessPath?.isDereferencedAt(expr: JcExpr): Boolean { if (this == null) { return false } if (expr is JcInstanceCallExpr) { - val instancePath = expr.instance.toPathOrNull() + val instancePath = convertToPathOrNull(expr.instance) if (instancePath.startsWith(this)) { return true } } if (expr is JcLengthExpr) { - val arrayPath = expr.array.toPathOrNull() + val arrayPath = convertToPathOrNull(expr.array) if (arrayPath.startsWith(this)) { return true } } - return expr - .getValues() - .mapNotNull { it.toPathOrNull() } + return expr.values + .mapNotNull { convertToPathOrNull(it) } .any { (it - this)?.isNotEmpty() == true } } -context(Traits) -internal fun AccessPath?.isDereferencedAt(inst: CommonInst): Boolean { +context(JcTraits) +internal fun AccessPath?.isDereferencedAt(inst: JcInst): Boolean { if (this == null) return false - return inst.getOperands().any { isDereferencedAt(it) } + return inst.operands.any { isDereferencedAt(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 6eb06d3033..d07548dd40 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 @@ -1,8 +1,7 @@ package org.usvm.dataflow.jvm.taint -import org.jacodb.api.common.analysis.ApplicationGraph -import org.jacodb.api.jvm.JcClasspath import org.jacodb.api.jvm.JcMethod +import org.jacodb.api.jvm.analysis.JcApplicationGraph import org.jacodb.api.jvm.cfg.JcInst import org.jacodb.taint.configuration.TaintConfigurationFeature import org.jacodb.taint.configuration.TaintConfigurationItem @@ -11,13 +10,13 @@ import org.usvm.dataflow.jvm.util.JcTraits import org.usvm.dataflow.taint.TaintManager fun jcTaintManager( - graph: ApplicationGraph, + graph: JcApplicationGraph, unitResolver: JcUnitResolver, useBidiRunner: Boolean = false, - getConfigForMethod: ((JcMethod) -> List?)? = null -): TaintManager = with(JcTraits) { + getConfigForMethod: ((JcMethod) -> List?)? = null, +): TaintManager = with(JcTraits(graph.cp)) { val config: (JcMethod) -> List? = getConfigForMethod ?: run { - val taintConfigurationFeature = (graph.project as JcClasspath).features + val taintConfigurationFeature = graph.cp.features ?.singleOrNull { it is TaintConfigurationFeature } ?.let { it as TaintConfigurationFeature } 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 92082d8692..4612a339f6 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 @@ -16,17 +16,16 @@ package org.usvm.dataflow.jvm.unused -import org.usvm.dataflow.ifds.FlowFunction -import org.usvm.dataflow.ifds.FlowFunctions -import org.usvm.dataflow.ifds.isOnHeap -import org.usvm.dataflow.util.Traits import org.jacodb.api.common.CommonMethod -import org.jacodb.api.common.CommonProject import org.jacodb.api.common.analysis.ApplicationGraph import org.jacodb.api.common.cfg.CommonAssignInst import org.jacodb.api.common.cfg.CommonInst import org.jacodb.api.jvm.cfg.JcSpecialCallExpr import org.jacodb.api.jvm.cfg.JcStaticCallExpr +import org.usvm.dataflow.ifds.FlowFunction +import org.usvm.dataflow.ifds.FlowFunctions +import org.usvm.dataflow.ifds.isOnHeap +import org.usvm.dataflow.util.Traits context(Traits) class UnusedVariableFlowFunctions( @@ -35,9 +34,6 @@ class UnusedVariableFlowFunctions( where Method : CommonMethod, Statement : CommonInst { - private val cp: CommonProject - get() = graph.project - override fun obtainPossibleStartFacts( method: Method, ): Collection { @@ -53,7 +49,7 @@ class UnusedVariableFlowFunctions( } if (fact == UnusedVariableZeroFact) { - val toPath = current.lhv.toPath() + val toPath = convertToPath(current.lhv) if (!toPath.isOnHeap) { return@FlowFunction setOf(UnusedVariableZeroFact, UnusedVariable(toPath, current)) } else { @@ -62,9 +58,9 @@ class UnusedVariableFlowFunctions( } check(fact is UnusedVariable) - val toPath = current.lhv.toPath() + val toPath = convertToPath(current.lhv) val default = if (toPath == fact.variable) emptySet() else setOf(fact) - val fromPath = current.rhv.toPathOrNull() + val fromPath = convertToPathOrNull(current.rhv) ?: return@FlowFunction default if (fromPath.isOnHeap || toPath.isOnHeap) { @@ -87,7 +83,7 @@ class UnusedVariableFlowFunctions( callStatement: Statement, calleeStart: Statement, ) = FlowFunction { fact -> - val callExpr = callStatement.getCallExpr() + val callExpr = getCallExpr(callStatement) ?: error("Call statement should have non-null callExpr") if (fact == UnusedVariableZeroFact) { @@ -98,9 +94,9 @@ class UnusedVariableFlowFunctions( return@FlowFunction buildSet { add(UnusedVariableZeroFact) val callee = graph.methodOf(calleeStart) - val formalParams = cp.getArgumentsOf(callee) + val formalParams = getArgumentsOf(callee) for (formal in formalParams) { - add(UnusedVariable(formal.toPath(), callStatement)) + add(UnusedVariable(convertToPath(formal), callStatement)) } } } 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 38e2c758ff..3571cdfbfd 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 @@ -16,8 +16,6 @@ package org.usvm.dataflow.jvm.unused -import org.usvm.dataflow.ifds.AccessPath -import org.usvm.dataflow.util.Traits import org.jacodb.api.common.CommonMethod import org.jacodb.api.common.cfg.CommonExpr import org.jacodb.api.common.cfg.CommonInst @@ -28,19 +26,23 @@ import org.jacodb.api.jvm.cfg.JcInst import org.jacodb.api.jvm.cfg.JcLocal import org.jacodb.api.jvm.cfg.JcSpecialCallExpr 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( expr: CommonExpr, ): Boolean { - return expr.getValues().any { it.toPathOrNull() == this } + return getValues(expr).any { + convertToPathOrNull(it) == this + } } context(Traits) internal fun AccessPath.isUsedAt( inst: CommonInst, ): Boolean { - val callExpr = inst.getCallExpr() + val callExpr = getCallExpr(inst) if (callExpr != null) { // Don't count constructor calls as usages diff --git a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/util/JcTraits.kt b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/util/JcTraits.kt index 37dffefa53..4a07d84a85 100644 --- a/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/util/JcTraits.kt +++ b/usvm-jvm-dataflow/src/main/kotlin/org/usvm/dataflow/jvm/util/JcTraits.kt @@ -17,7 +17,6 @@ package org.usvm.dataflow.jvm.util import org.jacodb.api.common.CommonMethodParameter -import org.jacodb.api.common.CommonProject import org.jacodb.api.common.cfg.CommonAssignInst import org.jacodb.api.common.cfg.CommonCallExpr import org.jacodb.api.common.cfg.CommonExpr @@ -46,6 +45,7 @@ import org.jacodb.api.jvm.cfg.JcStringConstant import org.jacodb.api.jvm.cfg.JcThis import org.jacodb.api.jvm.cfg.JcValue import org.jacodb.api.jvm.cfg.values +import org.jacodb.api.jvm.ext.cfg.callExpr import org.jacodb.api.jvm.ext.isAssignable import org.jacodb.api.jvm.ext.toType import org.jacodb.impl.cfg.util.loops @@ -57,192 +57,177 @@ import org.jacodb.taint.configuration.TypeMatches import org.usvm.dataflow.ifds.AccessPath import org.usvm.dataflow.ifds.ElementAccessor import org.usvm.dataflow.ifds.FieldAccessor -import org.usvm.dataflow.jvm.util.JcTraits.Companion.getArgument -import org.usvm.dataflow.jvm.util.JcTraits.Companion.toPathOrNull import org.usvm.dataflow.util.Traits -import org.jacodb.api.jvm.ext.cfg.callExpr as _callExpr -import org.usvm.dataflow.jvm.util.callee as _callee -import org.usvm.dataflow.jvm.util.getArgument as _getArgument -import org.usvm.dataflow.jvm.util.getArgumentsOf as _getArgumentsOf -import org.usvm.dataflow.jvm.util.thisInstance as _thisInstance -import org.usvm.dataflow.jvm.util.toPath as _toPath -import org.usvm.dataflow.jvm.util.toPathOrNull as _toPathOrNull /** * JVM-specific extensions for analysis. - * - * ### Usage: - * ``` - * class MyClass { - * companion object : JcTraits - * } - * ``` */ -interface JcTraits : Traits { +class JcTraits( + val cp: JcClasspath, +) : Traits { - override val JcMethod.thisInstance: JcThis - get() = _thisInstance - - @Suppress("EXTENSION_SHADOWED_BY_MEMBER") - override val JcMethod.isConstructor: Boolean - get() = isConstructor + override fun convertToPathOrNull(expr: CommonExpr): AccessPath? { + check(expr is JcExpr) + return expr.toPathOrNull() + } - override fun CommonExpr.toPathOrNull(): AccessPath? { - check(this is JcExpr) - return _toPathOrNull() + override fun convertToPathOrNull(value: CommonValue): AccessPath? { + check(value is JcValue) + return value.toPathOrNull() } - override fun CommonValue.toPathOrNull(): AccessPath? { - check(this is JcValue) - return _toPathOrNull() + override fun convertToPath(value: CommonValue): AccessPath { + check(value is JcValue) + return value.toPath() } - override fun CommonValue.toPath(): AccessPath { - check(this is JcValue) - return _toPath() + override fun getThisInstance(method: JcMethod): JcThis { + return method.thisInstance } - override val CommonCallExpr.callee: JcMethod - get() { - check(this is JcCallExpr) - return _callee - } + override fun isConstructor(method: JcMethod): Boolean { + return method.isConstructor + } - override fun CommonProject.getArgument(param: CommonMethodParameter): JcArgument? { - check(this is JcClasspath) + override fun getArgument(param: CommonMethodParameter): JcArgument? { check(param is JcParameter) - return _getArgument(param) + return cp.getArgument(param) + } + + override fun getArgumentsOf(method: JcMethod): List { + return cp.getArgumentsOf(method) + } + + override fun getCallee(callExpr: CommonCallExpr): JcMethod { + check(callExpr is JcCallExpr) + return callExpr.callee + } + + override fun getCallExpr(statement: JcInst): JcCallExpr? { + return statement.callExpr + } + + override fun getValues(expr: CommonExpr): Set { + check(expr is JcExpr) + return expr.values + } + + override fun getOperands(statement: JcInst): List { + return statement.operands } - override fun CommonProject.getArgumentsOf(method: JcMethod): List { - check(this is JcClasspath) - return _getArgumentsOf(method) + override fun getArrayAllocation(statement: JcInst): JcExpr? { + if (statement !is JcAssignInst) return null + return statement.rhv as? JcNewArrayExpr + } + + override fun getArrayAccessIndex(statement: JcInst): JcValue? { + if (statement !is JcAssignInst) return null + + val lhv = statement.lhv + if (lhv is JcArrayAccess) return lhv.index + + val rhv = statement.rhv + if (rhv is JcArrayAccess) return rhv.index + + return null } - override fun CommonValue.isConstant(): Boolean { - check(this is JcValue) - return this is JcConstant + override fun getBranchExprCondition(statement: JcInst): JcExpr? { + if (statement !is JcIfInst) return null + return statement.condition } - override fun CommonValue.eqConstant(constant: ConstantValue): Boolean { - check(this is JcValue) + override fun isConstant(value: CommonValue): Boolean { + check(value is JcValue) + return value is JcConstant + } + + override fun eqConstant(value: CommonValue, constant: ConstantValue): Boolean { + check(value is JcValue) return when (constant) { is ConstantBooleanValue -> { - this is JcBool && value == constant.value + value is JcBool && value.value == constant.value } is ConstantIntValue -> { - this is JcInt && value == constant.value + value is JcInt && value.value == constant.value } is ConstantStringValue -> { // TODO: if 'value' is not string, convert it to string and compare with 'constant.value' - this is JcStringConstant && value == constant.value + value is JcStringConstant && value.value == constant.value } } } - override fun CommonValue.ltConstant(constant: ConstantValue): Boolean { - check(this is JcValue) + override fun ltConstant(value: CommonValue, constant: ConstantValue): Boolean { + check(value is JcValue) return when (constant) { is ConstantIntValue -> { - this is JcInt && value < constant.value + value is JcInt && value.value < constant.value } else -> error("Unexpected constant: $constant") } } - override fun CommonValue.gtConstant(constant: ConstantValue): Boolean { - check(this is JcValue) + override fun gtConstant(value: CommonValue, constant: ConstantValue): Boolean { + check(value is JcValue) return when (constant) { is ConstantIntValue -> { - this is JcInt && value > constant.value + value is JcInt && value.value > constant.value } else -> error("Unexpected constant: $constant") } } - override fun CommonValue.matches(pattern: String): Boolean { - check(this is JcValue) - val s = this.toString() + override fun matches(value: CommonValue, pattern: String): Boolean { + check(value is JcValue) + val s = value.toString() val re = pattern.toRegex() return re.matches(s) } - override fun JcInst.getCallExpr(): CommonCallExpr? { - return _callExpr - } - - override fun CommonExpr.getValues(): Set { - check(this is JcExpr) - return values - } - - override fun JcInst.getOperands(): List { - return operands + override fun typeMatches(value: CommonValue, condition: TypeMatches): Boolean { + check(value is JcValue) + return value.type.isAssignable(condition.type) } - override fun JcInst.isLoopHead(): Boolean { - val loops = location.method.flowGraph().loops - return loops.any { loop -> this == loop.head } + override fun isLoopHead(statement: JcInst): Boolean { + val loops = statement.location.method.flowGraph().loops + return loops.any { loop -> statement == loop.head } } - override fun JcInst.getBranchExprCondition(): CommonExpr? { - if (this !is JcIfInst) return null - return condition + override fun lineNumber(statement: JcInst): Int { + return statement.lineNumber } - override fun JcInst.getArrayAllocation(): CommonExpr? { - if (this !is JcAssignInst) return null - return rhv as? JcNewArrayExpr - } - - override fun JcInst.getArrayAccessIndex(): CommonValue? { - if (this !is JcAssignInst) return null - - val lhv = this.lhv - if (lhv is JcArrayAccess) return lhv.index - - val rhv = this.rhv - if (rhv is JcArrayAccess) return rhv.index - - return null - } - - override fun JcInst.lineNumber(): Int? = location.lineNumber - - override fun JcInst.locationFQN(): String? { - val method = location.method + override fun locationFQN(statement: JcInst): String { + val method = statement.location.method return "${method.enclosingClass.name}#${method.name}" } - override fun CommonValue.typeMatches(condition: TypeMatches): Boolean { - check(this is JcValue) - return this.type.isAssignable(condition.type) - } - - override fun CommonAssignInst.taintFlowRhsValues(): List = - when (val rhs = this.rhv as JcExpr) { - is JcBinaryExpr -> listOf(rhs.lhv, rhs.rhv) - is JcNegExpr -> listOf(rhs.operand) - is JcCastExpr -> listOf(rhs.operand) - else -> listOf(rhs) + override fun taintFlowRhsValues(statement: CommonAssignInst): List { + check(statement is JcAssignInst) + return when (val rhv = statement.rhv) { + is JcBinaryExpr -> listOf(rhv.lhv, rhv.rhv) + is JcNegExpr -> listOf(rhv.operand) + is JcCastExpr -> listOf(rhv.operand) + else -> listOf(rhv) } + } - override fun JcInst.taintPassThrough(): List>? { - if (this !is JcAssignInst) return null + override fun taintPassThrough(statement: JcInst): List>? { + if (statement !is JcAssignInst) return null // FIXME: handle taint pass-through on invokedynamic-based String concatenation: - val callExpr = rhv as? JcDynamicCallExpr ?: return null + val callExpr = statement.rhv as? JcDynamicCallExpr ?: return null if (callExpr.callee.enclosingClass.name != "java.lang.invoke.StringConcatFactory") return null - return callExpr.args.map { it to this.lhv } + return callExpr.args.map { it to statement.lhv } } - - // Ensure that all methods are default-implemented in the interface itself: - companion object : JcTraits } val JcMethod.thisInstance: JcThis 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 eddc89946c..1b58239773 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 @@ -63,9 +63,8 @@ import kotlin.test.assertTrue class ConditionEvaluatorTest { - companion object : JcTraits - private val cp = mockk() + private val traits = JcTraits(cp) private val intType: JcPrimitiveType = PredefinedPrimitive(cp, PredefinedPrimitives.Int) private val boolType: JcPrimitiveType = PredefinedPrimitive(cp, PredefinedPrimitives.Boolean) @@ -94,7 +93,9 @@ class ConditionEvaluatorTest { else -> null }.toMaybe() } - private val evaluator: ConditionVisitor = BasicConditionEvaluator(positionResolver) + private val evaluator: ConditionVisitor = with(traits) { + BasicConditionEvaluator(positionResolver) + } @Test fun `True is true`() { @@ -328,12 +329,14 @@ class ConditionEvaluatorTest { @Test fun `FactAwareConditionEvaluator supports ContainsMark`() { - val fact = Tainted(intValue.toPath(), TaintMark("FOO")) - val factAwareEvaluator = FactAwareConditionEvaluator(fact, positionResolver) - assertTrue(factAwareEvaluator.visit(ContainsMark(intArg, TaintMark("FOO")))) - assertFalse(factAwareEvaluator.visit(ContainsMark(intArg, TaintMark("BAR")))) - assertFalse(factAwareEvaluator.visit(ContainsMark(stringArg, TaintMark("FOO")))) - assertFalse(factAwareEvaluator.visit(ContainsMark(stringArg, TaintMark("BAR")))) - assertFalse(factAwareEvaluator.visit(ContainsMark(position = mockk(), TaintMark("FOO")))) + with(traits) { + val fact = Tainted(convertToPath(intValue), TaintMark("FOO")) + val factAwareEvaluator = FactAwareConditionEvaluator(fact, positionResolver) + assertTrue(factAwareEvaluator.visit(ContainsMark(intArg, TaintMark("FOO")))) + assertFalse(factAwareEvaluator.visit(ContainsMark(intArg, TaintMark("BAR")))) + assertFalse(factAwareEvaluator.visit(ContainsMark(stringArg, TaintMark("FOO")))) + assertFalse(factAwareEvaluator.visit(ContainsMark(stringArg, TaintMark("BAR")))) + assertFalse(factAwareEvaluator.visit(ContainsMark(position = mockk(), TaintMark("FOO")))) + } } } diff --git a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsNpeTest.kt b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsNpeTest.kt index a75472fd71..71836d4f14 100644 --- a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsNpeTest.kt +++ b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsNpeTest.kt @@ -43,6 +43,7 @@ private val logger = mu.KotlinLogging.logger {} @TestInstance(PER_CLASS) class IfdsNpeTest : BaseAnalysisTest() { + fun provideClassesForJuliet476(): Stream = provideClassesForJuliet(476, listOf("null_check_after_deref")) 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 062cd67385..2f10ed7f45 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 @@ -41,7 +41,6 @@ private val logger = mu.KotlinLogging.logger {} @TestInstance(PER_CLASS) class IfdsSqlTest : BaseAnalysisTest() { - companion object: JcTraits fun provideClassesForJuliet89(): Stream = provideClassesForJuliet(89, specificBansCwe89) @@ -95,10 +94,12 @@ class IfdsSqlTest : BaseAnalysisTest() { val sinks = manager.analyze(listOf(badMethod), timeout = 30.seconds) assertTrue(sinks.isNotEmpty()) val sink = sinks.first() - val graph = manager.vulnerabilityTraceGraph(sink) - val trace = graph.getAllTraces().first() + val traceGraph = manager.vulnerabilityTraceGraph(sink) + val trace = traceGraph.getAllTraces().first() assertTrue(trace.isNotEmpty()) - val sarif = sarifReportFromVulnerabilities(listOf(sink.toSarif(graph))) + val sarif = with(JcTraits(graph.cp)) { + sarifReportFromVulnerabilities(listOf(sink.toSarif(traceGraph))) + } val json = Json { prettyPrint = true } val sarifJson = json.encodeToString(sarif) diff --git a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsUntrustedLoopBoundTest.kt b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsUntrustedLoopBoundTest.kt index 94686e2df5..e2b01e0499 100644 --- a/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsUntrustedLoopBoundTest.kt +++ b/usvm-jvm-dataflow/src/test/kotlin/org/usvm/dataflow/jvm/impl/IfdsUntrustedLoopBoundTest.kt @@ -32,6 +32,7 @@ private val logger = KotlinLogging.logger {} @TestInstance(PER_CLASS) class Ifds2UpperBoundTest : BaseAnalysisTest(configFileName = "config_untrusted_loop_bound.json") { + @Test fun `analyze untrusted upper bound`() { TaintAnalysisOptions.UNTRUSTED_LOOP_BOUND_SINK = 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 2067746d53..81cb5c1138 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 @@ -33,7 +33,6 @@ import kotlin.time.Duration.Companion.seconds @TestInstance(PER_CLASS) class IfdsUnusedTest : BaseAnalysisTest() { - companion object : JcTraits fun provideClassesForJuliet563(): Stream = provideClassesForJuliet( 563, listOf( @@ -59,7 +58,9 @@ class IfdsUnusedTest : BaseAnalysisTest() { fun `test on Juliet's CWE 563`(className: String) { testSingleJulietClass(className) { method -> val unitResolver = SingletonUnitResolver - val manager = UnusedVariableManager(graph, unitResolver) + val manager = with(JcTraits(cp)) { + UnusedVariableManager(graph, unitResolver) + } manager.analyze(listOf(method), timeout = 30.seconds) } } @@ -71,7 +72,9 @@ class IfdsUnusedTest : BaseAnalysisTest() { val clazz = cp.findClass(className) val badMethod = clazz.methods.single { it.name == "bad" } val unitResolver = SingletonUnitResolver - val manager = UnusedVariableManager(graph, unitResolver) + val manager = with(JcTraits(cp)) { + UnusedVariableManager(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 8a953d4508..2615df0ea6 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 @@ -32,7 +32,6 @@ private val logger = mu.KotlinLogging.logger {} @TestInstance(PER_CLASS) class JodaDateTimeAnalysisTest : BaseAnalysisTest() { - companion object : JcTraits @Test fun `test taint analysis`() { @@ -59,7 +58,9 @@ class JodaDateTimeAnalysisTest : BaseAnalysisTest() { val clazz = cp.findClass() val methods = clazz.declaredMethods val unitResolver = SingletonUnitResolver - val manager = UnusedVariableManager(graph, unitResolver) + val manager = with(JcTraits(cp)) { + UnusedVariableManager(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 03ca447284..c6e1c59622 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 @@ -29,6 +29,7 @@ import org.jacodb.api.jvm.cfg.JcInst import org.jacodb.api.jvm.cfg.JcLocal import org.jacodb.api.jvm.cfg.JcLocalVar import org.jacodb.api.jvm.cfg.JcReturnInst +import org.jacodb.api.jvm.ext.cfg.callExpr import org.jacodb.api.jvm.ext.findTypeOrNull import org.jacodb.api.jvm.ext.packageName import org.jacodb.taint.configuration.TaintConfigurationFeature @@ -39,18 +40,19 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS import org.usvm.dataflow.jvm.util.JcTraits +import org.usvm.dataflow.jvm.util.callee +import org.usvm.dataflow.jvm.util.toPath import org.usvm.dataflow.taint.ForwardTaintFlowFunctions import org.usvm.dataflow.taint.TaintZeroFact import org.usvm.dataflow.taint.Tainted @TestInstance(PER_CLASS) open class TaintFlowFunctionsTest : BaseAnalysisTest(configFileName = "config_test.json") { - companion object : JcTraits override val graph: JcApplicationGraph = mockk { - every { project } returns cp + every { cp } returns this@TaintFlowFunctionsTest.cp every { callees(any()) } answers { - sequenceOf(arg(0).getCallExpr()!!.callee) + sequenceOf(arg(0).callExpr!!.callee) } every { methodOf(any()) } answers { arg(0).location.method @@ -92,118 +94,132 @@ open class TaintFlowFunctionsTest : BaseAnalysisTest(configFileName = "config_te @Test fun `test obtain start facts`() { - val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) - val facts = flowSpace.obtainPossibleStartFacts(testMethod).toList() - val arg0 = cp.getArgument(testMethod.parameters[0])!! - val arg0Taint = Tainted(arg0.toPath(), TaintMark("EXAMPLE")) - Assertions.assertEquals(listOf(TaintZeroFact, arg0Taint), facts) + with(JcTraits(cp)) { + val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) + val facts = flowSpace.obtainPossibleStartFacts(testMethod).toList() + val arg0 = getArgument(testMethod.parameters[0])!! + val arg0Taint = Tainted(arg0.toPath(), TaintMark("EXAMPLE")) + Assertions.assertEquals(listOf(TaintZeroFact, arg0Taint), facts) + } } @Test fun `test sequential flow function assign mark`() { - // "x := y", where 'y' is tainted, should result in both 'x' and 'y' to be tainted - 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 f = flowSpace.obtainSequentFlowFunction(inst, next = mockk()) - val yTaint = Tainted(y.toPath(), TaintMark("TAINT")) - val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) - val facts = f.compute(yTaint).toList() - Assertions.assertEquals(listOf(yTaint, xTaint), facts) + with(JcTraits(cp)) { + // "x := y", where 'y' is tainted, should result in both 'x' and 'y' to be tainted + 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 f = flowSpace.obtainSequentFlowFunction(inst, next = mockk()) + val yTaint = Tainted(y.toPath(), TaintMark("TAINT")) + val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) + val facts = f.compute(yTaint).toList() + Assertions.assertEquals(listOf(yTaint, xTaint), facts) + } } @Test fun `test call flow function assign mark`() { - // "x := test(...)", where 'test' is a source, should result in 'x' to be tainted - val x: JcLocal = JcLocalVar(1, "x", stringType) - val callStatement = JcAssignInst(location = mockk(), lhv = x, rhv = mockk { - every { callee } returns testMethod - }) - val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) - val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) - val xTaint = Tainted(x.toPath(), TaintMark("EXAMPLE")) - val facts = f.compute(TaintZeroFact).toList() - Assertions.assertEquals(listOf(TaintZeroFact, xTaint), facts) + with(JcTraits(cp)) { + // "x := test(...)", where 'test' is a source, should result in 'x' to be tainted + val x: JcLocal = JcLocalVar(1, "x", stringType) + val callStatement = JcAssignInst(location = mockk(), lhv = x, rhv = mockk { + every { callee } returns testMethod + }) + val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) + val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) + val xTaint = Tainted(x.toPath(), TaintMark("EXAMPLE")) + val facts = f.compute(TaintZeroFact).toList() + Assertions.assertEquals(listOf(TaintZeroFact, xTaint), facts) + } } @Test fun `test call flow function remove mark`() { - // "test(x)", where 'x' is tainted, should result in 'x' NOT to be tainted - val x: JcLocal = JcLocalVar(1, "x", stringType) - val callStatement = JcCallInst(location = mockk(), callExpr = mockk { - every { callee } returns testMethod - every { args } returns listOf(x) - }) - val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) - val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) - val xTaint = Tainted(x.toPath(), TaintMark("REMOVE")) - val facts = f.compute(xTaint).toList() - Assertions.assertTrue(facts.isEmpty()) + with(JcTraits(cp)) { + // "test(x)", where 'x' is tainted, should result in 'x' NOT to be tainted + val x: JcLocal = JcLocalVar(1, "x", stringType) + val callStatement = JcCallInst(location = mockk(), callExpr = mockk { + every { callee } returns testMethod + every { args } returns listOf(x) + }) + val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) + val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) + val xTaint = Tainted(x.toPath(), TaintMark("REMOVE")) + val facts = f.compute(xTaint).toList() + Assertions.assertTrue(facts.isEmpty()) + } } @Test fun `test call flow function copy mark`() { - // "y := test(x)" should result in 'y' to be tainted only when 'x' is tainted - val x: JcLocal = JcLocalVar(1, "x", stringType) - val y: JcLocal = JcLocalVar(2, "y", stringType) - val callStatement = JcAssignInst(location = mockk(), lhv = y, rhv = mockk { - every { callee } returns testMethod - every { args } returns listOf(x) - }) - val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) - val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) - val xTaint = Tainted(x.toPath(), TaintMark("COPY")) - val yTaint = Tainted(y.toPath(), TaintMark("COPY")) - val facts = f.compute(xTaint).toList() - Assertions.assertEquals(listOf(xTaint, yTaint), facts) // copy from x to y - val other: JcLocal = JcLocalVar(10, "other", stringType) - val otherTaint = Tainted(other.toPath(), TaintMark("OTHER")) - val facts2 = f.compute(otherTaint).toList() - Assertions.assertEquals(listOf(otherTaint), facts2) // pass-through + with(JcTraits(cp)) { + // "y := test(x)" should result in 'y' to be tainted only when 'x' is tainted + val x: JcLocal = JcLocalVar(1, "x", stringType) + val y: JcLocal = JcLocalVar(2, "y", stringType) + val callStatement = JcAssignInst(location = mockk(), lhv = y, rhv = mockk { + every { callee } returns testMethod + every { args } returns listOf(x) + }) + val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) + val f = flowSpace.obtainCallToReturnSiteFlowFunction(callStatement, returnSite = mockk()) + val xTaint = Tainted(x.toPath(), TaintMark("COPY")) + val yTaint = Tainted(y.toPath(), TaintMark("COPY")) + val facts = f.compute(xTaint).toList() + Assertions.assertEquals(listOf(xTaint, yTaint), facts) // copy from x to y + val other: JcLocal = JcLocalVar(10, "other", stringType) + val otherTaint = Tainted(other.toPath(), TaintMark("OTHER")) + val facts2 = f.compute(otherTaint).toList() + Assertions.assertEquals(listOf(otherTaint), facts2) // pass-through + } } @Test fun `test call to start flow function`() { - // "test(x)", where 'x' is tainted, should result in 'x' (formal argument of 'test') to be tainted - val x: JcLocal = JcLocalVar(1, "x", stringType) - val callStatement = JcCallInst(location = mockk(), callExpr = mockk { - every { callee } returns testMethod - every { args } returns listOf(x) - }) - val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) - val f = flowSpace.obtainCallToStartFlowFunction(callStatement, calleeStart = mockk { - every { location } returns mockk { - every { method } returns testMethod - } - }) - val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) - val arg0: JcArgument = cp.getArgument(testMethod.parameters[0])!! - val arg0Taint = Tainted(arg0.toPath(), TaintMark("TAINT")) - val facts = f.compute(xTaint).toList() - Assertions.assertEquals(listOf(arg0Taint), facts) - val other: JcLocal = JcLocalVar(10, "other", stringType) - val otherTaint = Tainted(other.toPath(), TaintMark("TAINT")) - val facts2 = f.compute(otherTaint).toList() - Assertions.assertTrue(facts2.isEmpty()) + with(JcTraits(cp)) { + // "test(x)", where 'x' is tainted, should result in 'x' (formal argument of 'test') to be tainted + val x: JcLocal = JcLocalVar(1, "x", stringType) + val callStatement = JcCallInst(location = mockk(), callExpr = mockk { + every { callee } returns testMethod + every { args } returns listOf(x) + }) + val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) + val f = flowSpace.obtainCallToStartFlowFunction(callStatement, calleeStart = mockk { + every { location } returns mockk { + every { method } returns testMethod + } + }) + val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) + val arg0: JcArgument = getArgument(testMethod.parameters[0])!! + val arg0Taint = Tainted(arg0.toPath(), TaintMark("TAINT")) + val facts = f.compute(xTaint).toList() + Assertions.assertEquals(listOf(arg0Taint), facts) + val other: JcLocal = JcLocalVar(10, "other", stringType) + val otherTaint = Tainted(other.toPath(), TaintMark("TAINT")) + val facts2 = f.compute(otherTaint).toList() + Assertions.assertTrue(facts2.isEmpty()) + } } @Test fun `test exit flow function`() { - // "x := test()" + "return y", where 'y' is tainted, should result in 'x' to be tainted - val x: JcLocal = JcLocalVar(1, "x", stringType) - val callStatement = JcAssignInst(location = mockk(), lhv = x, rhv = mockk { - every { callee } returns testMethod - }) - val y: JcLocal = JcLocalVar(1, "y", stringType) - val exitStatement = JcReturnInst(location = mockk { - every { method } returns testMethod - }, returnValue = y) - val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) - val f = flowSpace.obtainExitToReturnSiteFlowFunction(callStatement, returnSite = mockk(), exitStatement) - val yTaint = Tainted(y.toPath(), TaintMark("TAINT")) - val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) - val facts = f.compute(yTaint).toList() - Assertions.assertEquals(listOf(xTaint), facts) + with(JcTraits(cp)) { + // "x := test()" + "return y", where 'y' is tainted, should result in 'x' to be tainted + val x: JcLocal = JcLocalVar(1, "x", stringType) + val callStatement = JcAssignInst(location = mockk(), lhv = x, rhv = mockk { + every { callee } returns testMethod + }) + val y: JcLocal = JcLocalVar(1, "y", stringType) + val exitStatement = JcReturnInst(location = mockk { + every { method } returns testMethod + }, returnValue = y) + val flowSpace = ForwardTaintFlowFunctions(graph, getConfigForMethod) + val f = flowSpace.obtainExitToReturnSiteFlowFunction(callStatement, returnSite = mockk(), exitStatement) + val yTaint = Tainted(y.toPath(), TaintMark("TAINT")) + val xTaint = Tainted(x.toPath(), TaintMark("TAINT")) + val facts = f.compute(yTaint).toList() + Assertions.assertEquals(listOf(xTaint), facts) + } } } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/util/JcTestDecoders.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/util/JcTestDecoders.kt index 7e98f91ef4..fb5f049ee0 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/util/JcTestDecoders.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/util/JcTestDecoders.kt @@ -19,7 +19,7 @@ class JcTestDecoders(private val cp: JcClasspath) { return runBlocking { cp.hierarchyExt() - .findSubClasses(objectDecoder, allHierarchy = true, includeOwn = false) + .findSubClasses(objectDecoder, entireHierarchy = true, includeOwn = false) .mapNotNull { loadDecoder(it) } .toMap() } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcApplicationGraph.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcApplicationGraph.kt index 67a507e856..be7fe79888 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcApplicationGraph.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcApplicationGraph.kt @@ -1,12 +1,12 @@ package org.usvm.machine +import kotlinx.coroutines.runBlocking import org.jacodb.api.jvm.JcClasspath import org.jacodb.api.jvm.JcMethod import org.jacodb.api.jvm.JcTypedMethod import org.jacodb.api.jvm.cfg.JcInst import org.jacodb.api.jvm.ext.toType -import org.jacodb.impl.features.HierarchyExtensionImpl -import org.jacodb.impl.features.SyncUsagesExtension +import org.jacodb.impl.features.usagesExt import org.usvm.dataflow.jvm.graph.JcApplicationGraphImpl import org.usvm.statistics.ApplicationGraph import org.usvm.util.originalInst @@ -18,7 +18,7 @@ import java.util.concurrent.ConcurrentHashMap class JcApplicationGraph( cp: JcClasspath, ) : ApplicationGraph { - private val jcApplicationGraph = JcApplicationGraphImpl(cp, SyncUsagesExtension(HierarchyExtensionImpl(cp), cp)) + private val jcApplicationGraph = JcApplicationGraphImpl(cp, runBlocking { cp.usagesExt() }) override fun predecessors(node: JcInst): Sequence { return jcApplicationGraph.predecessors(node.originalInst()) diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcTypeSystem.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcTypeSystem.kt index e25703d845..521e993e73 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcTypeSystem.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcTypeSystem.kt @@ -1,5 +1,6 @@ package org.usvm.machine +import kotlinx.coroutines.runBlocking import org.jacodb.api.jvm.JcArrayType import org.jacodb.api.jvm.JcClassType import org.jacodb.api.jvm.JcClasspath @@ -10,7 +11,7 @@ import org.jacodb.api.jvm.JcTypeVariable import org.jacodb.api.jvm.ext.isAssignable import org.jacodb.api.jvm.ext.objectType import org.jacodb.api.jvm.ext.toType -import org.jacodb.impl.features.HierarchyExtensionImpl +import org.jacodb.impl.features.hierarchyExt import org.usvm.types.USupportTypeStream import org.usvm.types.UTypeStream import org.usvm.types.UTypeSystem @@ -20,7 +21,7 @@ class JcTypeSystem( private val cp: JcClasspath, override val typeOperationsTimeout: Duration ) : UTypeSystem { - private val hierarchy = HierarchyExtensionImpl(cp) + private val hierarchy = runBlocking { cp.hierarchyExt() } override fun isSupertype(supertype: JcType, type: JcType): Boolean = when { @@ -105,7 +106,7 @@ class JcTypeSystem( is JcRefType -> hierarchy .findSubClasses( type.jcClass, - allHierarchy = false + entireHierarchy = false, ) // TODO: prioritize classes somehow and filter bad classes .map { it.toType() } .run { diff --git a/usvm-jvm/src/test/kotlin/org/usvm/samples/approximations/ApproximationsTest.kt b/usvm-jvm/src/test/kotlin/org/usvm/samples/approximations/ApproximationsTest.kt index ea15066b12..766222eea7 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/samples/approximations/ApproximationsTest.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/samples/approximations/ApproximationsTest.kt @@ -59,7 +59,7 @@ class ApproximationsTest : ApproximationsTestRunner() { private fun approximationTests(): List { val allClasses = runBlocking { - cp.hierarchyExt().findSubClasses(cp.objectClass, allHierarchy = true, includeOwn = true) + cp.hierarchyExt().findSubClasses(cp.objectClass, entireHierarchy = true, includeOwn = true) } return allClasses .filter { cls -> From e3f6864cd9931024d321b4c8b8d7b2624ce035a2 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 17 Jul 2024 18:49:51 +0300 Subject: [PATCH 2/9] Bump jacodb version --- buildSrc/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index fce36ee7d6..11eaf3a849 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -6,7 +6,7 @@ object Versions { const val coroutines = "1.6.4" const val jacodbPackage = "com.github.UnitTestBot.jacodb" - const val jacodb = "5102242db4" // jacodb neo branch + const val jacodb = "e47c227815" const val mockk = "1.13.4" const val junitParams = "5.9.3" From 397535be62f3ac38d92c628c73fb13a3370893c2 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 17 Jul 2024 19:02:29 +0300 Subject: [PATCH 3/9] Format --- .../org/usvm/dataflow/taint/TaintFlowFunctions.kt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) 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 2d6d02e7aa..d6e9b8461c 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 @@ -17,7 +17,6 @@ package org.usvm.dataflow.taint import org.jacodb.api.common.CommonMethod -import org.jacodb.api.common.CommonProject import org.jacodb.api.common.analysis.ApplicationGraph import org.jacodb.api.common.cfg.CommonAssignInst import org.jacodb.api.common.cfg.CommonExpr @@ -73,10 +72,10 @@ class ForwardTaintFlowFunctions( val config = getConfigForMethod(method) if (config != null) { val conditionEvaluator = BasicConditionEvaluator( - EntryPointPositionToValueResolver(method) + EntryPointPositionToValueResolver(method) ) val actionEvaluator = TaintActionEvaluator( - EntryPointPositionToAccessPathResolver(method) + EntryPointPositionToAccessPathResolver(method) ) // Handle EntryPointSource config items: @@ -258,7 +257,7 @@ class ForwardTaintFlowFunctions( fact, CallPositionToValueResolver(callStatement) ) val actionEvaluator = TaintActionEvaluator( - CallPositionToAccessPathResolver(callStatement) + CallPositionToAccessPathResolver(callStatement) ) var defaultBehavior = true @@ -572,7 +571,7 @@ class BackwardTaintFlowFunctions( val callExpr = getCallExpr(callStatement) ?: error("Call statement should have non-null callExpr") - val callee = getCallee( callExpr) + val callee = getCallee(callExpr) if (callee in graph.callees(callStatement)) { @@ -582,14 +581,14 @@ class BackwardTaintFlowFunctions( for (actual in callExpr.args) { // Possibly tainted actual parameter: - if (fact.variable.startsWith(convertToPathOrNull( actual))) { + 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))) { + if (fact.variable.startsWith(convertToPathOrNull(callExpr.instance))) { return@FlowFunction emptyList() // Will be handled by summary edge } } @@ -598,7 +597,7 @@ class BackwardTaintFlowFunctions( if (callStatement is CommonAssignInst) { // Possibly tainted rhv: - if (fact.variable.startsWith(convertToPathOrNull( callStatement.rhv))) { + if (fact.variable.startsWith(convertToPathOrNull(callStatement.rhv))) { return@FlowFunction emptyList() // Overridden by lhv } } From 2f8aad72074d0adba6e41aa294e15a03ba00f648 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 17 Jul 2024 19:02:52 +0300 Subject: [PATCH 4/9] Disable UnusedVariable analysis tests --- .../test/kotlin/org/usvm/dataflow/jvm/impl/IfdsUnusedTest.kt | 2 ++ 1 file changed, 2 insertions(+) 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 81cb5c1138..3770129a85 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 @@ -19,6 +19,7 @@ package org.usvm.dataflow.jvm.impl import org.jacodb.api.jvm.ext.findClass import org.jacodb.api.jvm.ext.methods import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS @@ -53,6 +54,7 @@ class IfdsUnusedTest : BaseAnalysisTest() { ) ) + @Disabled("See https://github.com/UnitTestBot/jacodb/issues/220") @ParameterizedTest @MethodSource("provideClassesForJuliet563") fun `test on Juliet's CWE 563`(className: String) { From 598be2c48aff3b470cbab8c49595fc7fb47c8a4d Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 17 Jul 2024 19:11:27 +0300 Subject: [PATCH 5/9] Fix merge issues --- buildSrc/src/main/kotlin/Dependencies.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index b9952da31e..a2ae9c8bfe 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -5,7 +5,7 @@ import org.gradle.plugin.use.PluginDependenciesSpec object Versions { const val detekt = "1.18.1" const val ini4j = "0.5.4" - const val jacodb = "5102242db4" + const val jacodb = "e47c227815" const val juliet = "1.3.2" const val junit = "5.9.3" const val kotlin = "1.9.20" @@ -115,28 +115,29 @@ object Libs { ) // https://github.com/UnitTestBot/jacodb + private const val jacodbPackage = "com.github.UnitTestBot.jacodb" // use "org.jacodb" with includeBuild val jacodb_core = dep( - group = "com.github.UnitTestBot.jacodb", + group = jacodbPackage, name = "jacodb-core", version = Versions.jacodb ) val jacodb_api_jvm = dep( - group = "com.github.UnitTestBot.jacodb", + group = jacodbPackage, name = "jacodb-api-jvm", version = Versions.jacodb ) val jacodb_api_common = dep( - group = "com.github.UnitTestBot.jacodb", + group = jacodbPackage, name = "jacodb-api-common", version = Versions.jacodb ) val jacodb_approximations = dep( - group = "com.github.UnitTestBot.jacodb", + group = jacodbPackage, name = "jacodb-approximations", version = Versions.jacodb ) val jacodb_taint_configuration = dep( - group = "com.github.UnitTestBot.jacodb", + group = jacodbPackage, name = "jacodb-taint-configuration", version = Versions.jacodb ) From 6ed7537eb9e8972c8a2b548176fcc6c3f2715ac5 Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 17 Jul 2024 20:06:57 +0300 Subject: [PATCH 6/9] Fix generic type parameters --- .../main/kotlin/org/usvm/dataflow/taint/TaintFlowFunctions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d6e9b8461c..657b1c0a45 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 @@ -451,7 +451,7 @@ class ForwardTaintFlowFunctions( } } -context(Traits) +context(Traits) class BackwardTaintFlowFunctions( private val graph: ApplicationGraph, ) : FlowFunctions From bea6a3abc122de19a4a9f4c30c80497923ee2d0b Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Wed, 17 Jul 2024 23:10:44 +0300 Subject: [PATCH 7/9] Install `Usages` feature in `JacoDBContainer` --- usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt b/usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt index 487fa5497d..6edb1410ae 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt @@ -6,6 +6,7 @@ import org.jacodb.api.jvm.JcDatabase import org.jacodb.approximation.Approximations import org.jacodb.impl.JcSettings import org.jacodb.impl.features.InMemoryHierarchy +import org.jacodb.impl.features.Usages import org.jacodb.impl.jacodb import org.usvm.util.classpathWithApproximations import java.io.File @@ -56,7 +57,7 @@ class JacoDBContainer( private val defaultBuilder: JcSettings.() -> Unit = { useProcessJavaRuntime() - installFeatures(InMemoryHierarchy) + installFeatures(InMemoryHierarchy, Usages) } } } From ad6301c594f5a1555a86e51e5e64c2114a21b2fe Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Thu, 18 Jul 2024 11:38:38 +0300 Subject: [PATCH 8/9] Fix JcApplicationGraph initialization Remove Usages in JacoDBContainer. Use SyncUsagesExtension directly --- .../src/main/kotlin/org/usvm/machine/JcApplicationGraph.kt | 6 ++++-- .../src/test/kotlin/org/usvm/samples/JacoDBContainer.kt | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcApplicationGraph.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcApplicationGraph.kt index be7fe79888..03b0ddd3ee 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcApplicationGraph.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcApplicationGraph.kt @@ -6,7 +6,8 @@ import org.jacodb.api.jvm.JcMethod import org.jacodb.api.jvm.JcTypedMethod import org.jacodb.api.jvm.cfg.JcInst import org.jacodb.api.jvm.ext.toType -import org.jacodb.impl.features.usagesExt +import org.jacodb.impl.features.SyncUsagesExtension +import org.jacodb.impl.features.hierarchyExt import org.usvm.dataflow.jvm.graph.JcApplicationGraphImpl import org.usvm.statistics.ApplicationGraph import org.usvm.util.originalInst @@ -18,7 +19,8 @@ import java.util.concurrent.ConcurrentHashMap class JcApplicationGraph( cp: JcClasspath, ) : ApplicationGraph { - private val jcApplicationGraph = JcApplicationGraphImpl(cp, runBlocking { cp.usagesExt() }) + private val jcApplicationGraph = + JcApplicationGraphImpl(cp, SyncUsagesExtension(runBlocking { cp.hierarchyExt() }, cp)) override fun predecessors(node: JcInst): Sequence { return jcApplicationGraph.predecessors(node.originalInst()) diff --git a/usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt b/usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt index 6edb1410ae..748d90bbd5 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt @@ -57,7 +57,7 @@ class JacoDBContainer( private val defaultBuilder: JcSettings.() -> Unit = { useProcessJavaRuntime() - installFeatures(InMemoryHierarchy, Usages) + installFeatures(InMemoryHierarchy) } } } From e35ae6c0108624c6473327a0d9be73b6be198c9a Mon Sep 17 00:00:00 2001 From: Konstantin Chukharev Date: Thu, 18 Jul 2024 11:40:03 +0300 Subject: [PATCH 9/9] Remove unnecessary import --- usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt b/usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt index 748d90bbd5..487fa5497d 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/samples/JacoDBContainer.kt @@ -6,7 +6,6 @@ import org.jacodb.api.jvm.JcDatabase import org.jacodb.approximation.Approximations import org.jacodb.impl.JcSettings import org.jacodb.impl.features.InMemoryHierarchy -import org.jacodb.impl.features.Usages import org.jacodb.impl.jacodb import org.usvm.util.classpathWithApproximations import java.io.File