From ee0152ad4d0f880e38c13f7fd3ab822282f0b5ef Mon Sep 17 00:00:00 2001 From: Maksim Parshin Date: Sun, 27 Aug 2023 17:29:55 +0300 Subject: [PATCH 01/17] Add targets base infrastructure --- usvm-core/src/main/kotlin/org/usvm/Context.kt | 4 +- usvm-core/src/main/kotlin/org/usvm/Machine.kt | 5 +- .../src/main/kotlin/org/usvm/PathTrieNode.kt | 6 +- usvm-core/src/main/kotlin/org/usvm/State.kt | 30 ++++- .../src/main/kotlin/org/usvm/StepScope.kt | 2 +- usvm-core/src/main/kotlin/org/usvm/Targets.kt | 62 ++++++++++ ...StateWeighter.kt => DistanceCalculator.kt} | 48 ++++++-- .../ps/ExceptionPropagationPathSelector.kt | 2 +- .../kotlin/org/usvm/ps/PathSelectorFactory.kt | 73 ++++++++--- .../org/usvm/ps/RandomTreePathSelector.kt | 2 +- .../org/usvm/statistics/CoverageStatistics.kt | 2 +- .../TargetsReachedStatesCollector.kt | 14 +++ .../usvm/statistics/TerminatedStateRemover.kt | 2 +- .../src/test/kotlin/org/usvm/PathTests.kt | 18 +-- .../kotlin/org/usvm/TestApplicationGraph.kt | 115 ++++++++++++++++++ .../src/test/kotlin/org/usvm/TestUtil.kt | 27 +++- ...stToTargetsPathSelectorIntegrationTests.kt | 73 +++++++++++ .../usvm/ps/RandomTreePathSelectorTests.kt | 29 ++--- ...terprocShortestDistanceCalculatorTests.kt} | 78 ++++++------ .../org/usvm/api/targets/JcLocationTarget.kt | 20 +++ .../targets/JcNullPointerDereferenceTarget.kt | 16 +++ .../kotlin/org/usvm/api/targets/JcTarget.kt | 15 +++ .../main/kotlin/org/usvm/machine/JcContext.kt | 3 + .../main/kotlin/org/usvm/machine/JcMachine.kt | 49 ++++++-- .../machine/interpreter/JcExprResolver.kt | 5 +- .../usvm/machine/interpreter/JcInterpreter.kt | 5 +- .../interpreter/JcInterpreterObserver.kt | 27 ++++ .../kotlin/org/usvm/machine/state/JcState.kt | 9 +- .../targets/NullPointerDereference.java | 20 +++ .../targets/TestNullPointerDereference.kt | 38 ++++++ .../kotlin/org/usvm/machine/SampleState.kt | 9 +- .../kotlin/org/usvm/machine/SampleTarget.kt | 7 ++ .../main/kotlin/org/usvm/UMachineOptions.kt | 21 ++++ 33 files changed, 701 insertions(+), 135 deletions(-) create mode 100644 usvm-core/src/main/kotlin/org/usvm/Targets.kt rename usvm-core/src/main/kotlin/org/usvm/ps/{ShortestDistanceToTargetsStateWeighter.kt => DistanceCalculator.kt} (64%) create mode 100644 usvm-core/src/main/kotlin/org/usvm/statistics/TargetsReachedStatesCollector.kt create mode 100644 usvm-core/src/test/kotlin/org/usvm/TestApplicationGraph.kt create mode 100644 usvm-core/src/test/kotlin/org/usvm/ps/ClosestToTargetsPathSelectorIntegrationTests.kt rename usvm-core/src/test/kotlin/org/usvm/ps/{ShortestDistanceToTargetsStateWeighterTests.kt => RoughIterprocShortestDistanceCalculatorTests.kt} (55%) create mode 100644 usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt create mode 100644 usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt create mode 100644 usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt create mode 100644 usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreterObserver.kt create mode 100644 usvm-jvm/src/samples/java/org/usvm/samples/targets/NullPointerDereference.java create mode 100644 usvm-jvm/src/test/kotlin/org/usvm/samples/targets/TestNullPointerDereference.kt create mode 100644 usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/Context.kt b/usvm-core/src/main/kotlin/org/usvm/Context.kt index f791a18462..a1b4cc4edc 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Context.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Context.kt @@ -348,8 +348,8 @@ open class UContext( // Type hack to be able to intern the initial location for inheritors. private val initialLocation = RootNode() - fun , Statement> mkInitialLocation() - : PathsTrieNode = initialLocation.uncheckedCast() + fun , Statement> mkInitialLocation() + : PathsTrieNode = initialLocation.uncheckedCast() fun mkUValueSampler(): KSortVisitor> { return UValueSampler(this) diff --git a/usvm-core/src/main/kotlin/org/usvm/Machine.kt b/usvm-core/src/main/kotlin/org/usvm/Machine.kt index fc06780d47..15dbbbf50f 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Machine.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Machine.kt @@ -6,14 +6,13 @@ import org.usvm.stopstrategies.StopStrategy import org.usvm.util.bracket import org.usvm.util.debug +val logger = object : KLogging() {}.logger + /** * An abstract symbolic machine. * * @see [run] */ - -val logger = object : KLogging() {}.logger - abstract class UMachine : AutoCloseable { /** * Runs symbolic execution loop. diff --git a/usvm-core/src/main/kotlin/org/usvm/PathTrieNode.kt b/usvm-core/src/main/kotlin/org/usvm/PathTrieNode.kt index 948ae71ab2..13ae4d9b88 100644 --- a/usvm-core/src/main/kotlin/org/usvm/PathTrieNode.kt +++ b/usvm-core/src/main/kotlin/org/usvm/PathTrieNode.kt @@ -3,7 +3,7 @@ package org.usvm /** * Symbolic execution tree node. */ -sealed class PathsTrieNode, Statement> { +sealed class PathsTrieNode, Statement> { /** * Forked states' nodes. */ @@ -65,7 +65,7 @@ sealed class PathsTrieNode, Statement> } } -class PathsTrieNodeImpl, Statement> private constructor( +class PathsTrieNodeImpl, Statement> private constructor( override val depth: Int, override val states: MutableSet, // Note: order is important for tests @@ -101,7 +101,7 @@ class PathsTrieNodeImpl, Statement> pr override fun toString(): String = "Depth: $depth, statement: $statement" } -class RootNode, Statement> : PathsTrieNode() { +class RootNode, Statement> : PathsTrieNode() { override val children: MutableMap> = mutableMapOf() override val states: MutableSet = hashSetOf() diff --git a/usvm-core/src/main/kotlin/org/usvm/State.kt b/usvm-core/src/main/kotlin/org/usvm/State.kt index 0f175ab9fd..5090df9351 100644 --- a/usvm-core/src/main/kotlin/org/usvm/State.kt +++ b/usvm-core/src/main/kotlin/org/usvm/State.kt @@ -10,7 +10,7 @@ import org.usvm.solver.UUnsatResult typealias StateId = UInt -abstract class UState>( +abstract class UState, State : UState>( // TODO: add interpreter-specific information ctx: UContext, open val callStack: UCallStack, @@ -18,6 +18,7 @@ abstract class UState, open var models: List>, open var pathLocation: PathsTrieNode, + targets: List = emptyList() ) { /** * Deterministic state id. @@ -54,7 +55,7 @@ abstract class UState + other as UState<*, *, *, *, *, *, *> return id == other.id } @@ -73,6 +74,25 @@ abstract class UState() + + // TODO: clean removed targets sometimes + val targets: Collection get() = currentTargets.filterNot { it.isRemoved } + val reachedSinks: Set = reachedSinksImpl + + internal fun visitTarget(target: Target): Boolean { + if (currentTargets.remove(target) && !target.isRemoved) { + if (target.isSink) { + reachedSinksImpl.add(target) + } else { + currentTargets.addAll(target.children) + } + return true + } + return false + } } data class ForkResult( @@ -96,7 +116,7 @@ private const val OriginalState = false * forked state. * */ -private fun , Type, Context : UContext> forkIfSat( +private fun , Type, Field, Context : UContext> forkIfSat( state: T, newConstraintToOriginalState: UBoolExpr, newConstraintToForkedState: UBoolExpr, @@ -156,7 +176,7 @@ private fun , Type, Context : UContext> forkI * 2. makes not more than one query to USolver; * 3. if both [condition] and ![condition] are satisfiable, then [ForkResult.positiveState] === [state]. */ -fun , Type, Context : UContext> fork( +fun , Type, Field, Context : UContext> fork( state: T, condition: UBoolExpr, ): ForkResult { @@ -217,7 +237,7 @@ fun , Type, Context : UContext> fork( * @return a list of states for each condition - `null` state * means [UUnknownResult] or [UUnsatResult] of checking condition. */ -fun , Type, Context : UContext> forkMulti( +fun , Type, Field, Context : UContext> forkMulti( state: T, conditions: Iterable, ): List { diff --git a/usvm-core/src/main/kotlin/org/usvm/StepScope.kt b/usvm-core/src/main/kotlin/org/usvm/StepScope.kt index f018a8f6c9..6ca67948c4 100644 --- a/usvm-core/src/main/kotlin/org/usvm/StepScope.kt +++ b/usvm-core/src/main/kotlin/org/usvm/StepScope.kt @@ -18,7 +18,7 @@ import org.usvm.StepScope.StepScopeState.DEAD * * @param originalState an initial state. */ -class StepScope, Type, Context : UContext>( +class StepScope, Type, Field, Context : UContext>( private val originalState: T, ) { private val forkedStates = mutableListOf() diff --git a/usvm-core/src/main/kotlin/org/usvm/Targets.kt b/usvm-core/src/main/kotlin/org/usvm/Targets.kt new file mode 100644 index 0000000000..aab409bb5d --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/Targets.kt @@ -0,0 +1,62 @@ +package org.usvm + +import org.usvm.statistics.UMachineObserver + +abstract class UTarget, State : UState<*, *, Method, Statement, *, Target, State>>( + val method: Method, + val statement: Statement +) : UMachineObserver { + private val childrenImpl = mutableListOf() + private var parent: Target? = null + + val location = method to statement + + val children: List = childrenImpl + + val isSink get() = childrenImpl.isEmpty() + + var isRemoved = false + private set + + // TODO: avoid possible recursion + fun addChild(child: Target): Target { + check(!isRemoved) { "Cannot add child to removed target" } + require(child.parent == null) { "Cannot add child target with existing parent" } + childrenImpl.add(child) + @Suppress("UNCHECKED_CAST") + child.parent = this as Target + return child + } + + protected inline fun forEachChild(action: Target.() -> Unit) { + children.forEach { if (!it.isRemoved) it.action() } + } + + // TODO: think about naming + protected fun visit(byState: State) { + @Suppress("UNCHECKED_CAST") + if (byState.visitTarget(this as Target) && isSink) { + remove() + } + } + + private fun remove() { + check(childrenImpl.all { it.isRemoved }) { "Cannot remove target when some of its children are not removed" } + if (isRemoved) { + return + } + isRemoved = true + val parent = parent + if (parent != null && parent.childrenImpl.all { it.isRemoved }) { + parent.remove() + } + } + + override fun onState(parent: State, forks: Sequence) { + forEachChild { onState(parent, forks) } + } + + override fun onStateTerminated(state: State) { + forEachChild { onStateTerminated(state) } + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/ShortestDistanceToTargetsStateWeighter.kt b/usvm-core/src/main/kotlin/org/usvm/ps/DistanceCalculator.kt similarity index 64% rename from usvm-core/src/main/kotlin/org/usvm/ps/ShortestDistanceToTargetsStateWeighter.kt rename to usvm-core/src/main/kotlin/org/usvm/ps/DistanceCalculator.kt index 9e2f289718..f1c55dc66d 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/ShortestDistanceToTargetsStateWeighter.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/DistanceCalculator.kt @@ -1,11 +1,14 @@ package org.usvm.ps -import org.usvm.UState +import org.usvm.UCallStackFrame import kotlin.math.min +fun interface StaticTargetsDistanceCalculator { + fun calculateDistance(currentStatement: Statement, callStack: Collection>): Distance +} + /** - * [StateWeighter] implementation which weights states by their application graph - * distance to specified targets. + * Calculates shortest distances from location (represented as statement and call stack) to the set of targets. * * Distances in graph remain the same, only the targets can change, so the local CFG distances are * cached while the targets of the method remain the same. @@ -16,12 +19,13 @@ import kotlin.math.min * @param getCfgDistanceToExitPoint function with the following signature: * (method, stmt) -> shortest CFG distance from stmt to any of method's exit points. */ -class ShortestDistanceToTargetsStateWeighter>( +class RoughIterprocShortestDistanceCalculator( targets: Collection>, private val getCfgDistance: (Method, Statement, Statement) -> UInt, private val getCfgDistanceToExitPoint: (Method, Statement) -> UInt -) : StateWeighter { +) : StaticTargetsDistanceCalculator { + // TODO: optimize for single target case private val targetsByMethod = HashMap>() private val minLocalDistanceToTargetCache = HashMap>() @@ -55,12 +59,9 @@ class ShortestDistanceToTargetsStateWeighter>): UInt { var currentMinDistanceToTarget = UInt.MAX_VALUE - - val callStackArray = state.callStack.toTypedArray() + val callStackArray = callStack.toTypedArray() // minDistanceToTarget(F) = // min( @@ -90,3 +91,30 @@ class ShortestDistanceToTargetsStateWeighter( + private val getDistanceCalculator: (Method, Statement) -> StaticTargetsDistanceCalculator +) { + private val calculatorsByTarget = HashMap, StaticTargetsDistanceCalculator>() + + fun removeTargetFromCache(target: Pair): Boolean { + return calculatorsByTarget.remove(target) != null + } + + fun calculateDistance( + currentStatement: Statement, + callStack: Collection>, + target: Pair + ): Distance { + val calculator = calculatorsByTarget.computeIfAbsent(target) { getDistanceCalculator(it.first, it.second) } + return calculator.calculateDistance(currentStatement, callStack) + } + + fun calculateDistance( + currentStatement: Statement, + callStack: Collection>, + targets: Collection>, + folder: (Collection) -> Distance + ): Distance = + folder(targets.map { calculateDistance(currentStatement, callStack, it) }) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/ExceptionPropagationPathSelector.kt b/usvm-core/src/main/kotlin/org/usvm/ps/ExceptionPropagationPathSelector.kt index 95792f0eb7..0535147d99 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/ExceptionPropagationPathSelector.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/ExceptionPropagationPathSelector.kt @@ -7,7 +7,7 @@ import java.util.IdentityHashMap /** * A class designed to give the highest priority to the states containing exceptions. */ -class ExceptionPropagationPathSelector>( +class ExceptionPropagationPathSelector>( private val selector: UPathSelector, ) : UPathSelector { // An internal queue for states containing exceptions. diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt index 6a98e608ad..2e53a4cc92 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt @@ -1,10 +1,6 @@ package org.usvm.ps -import org.usvm.PathSelectionStrategy -import org.usvm.PathSelectorCombinationStrategy -import org.usvm.UMachineOptions -import org.usvm.UPathSelector -import org.usvm.UState +import org.usvm.* import org.usvm.statistics.CoverageStatistics import org.usvm.statistics.DistanceStatistics import org.usvm.algorithms.DeterministicPriorityCollection @@ -12,7 +8,7 @@ import org.usvm.algorithms.RandomizedPriorityCollection import kotlin.math.max import kotlin.random.Random -fun > createPathSelector( +fun > createPathSelector( initialState: State, options: UMachineOptions, coverageStatistics: () -> CoverageStatistics? = { null }, @@ -84,19 +80,34 @@ fun > createPa return selector } +fun , State : UState<*, *, Method, Statement, *, Target, State>> createTargetReproductionPathSelector( + initialState: State, + options: TargetReproductionOptions, + distanceStatistics: DistanceStatistics +): UPathSelector { + val random = + when(options.pathSelectionStrategy) { + TargetReproductionPathSelectionStrategy.RANDOMIZED -> Random(options.randomSeed) + TargetReproductionPathSelectionStrategy.DETERMINISTIC -> null + } + val selector = createClosestToTargetsPathSelector(distanceStatistics, random) + selector.add(listOf(initialState)) + return selector +} + /** * Wraps the selector into an [ExceptionPropagationPathSelector] if [propagateExceptions] is true. */ -private fun > UPathSelector.wrapIfRequired(propagateExceptions: Boolean) = +private fun > UPathSelector.wrapIfRequired(propagateExceptions: Boolean) = if (propagateExceptions && this !is ExceptionPropagationPathSelector) { ExceptionPropagationPathSelector(this) } else { this } -private fun > compareById(): Comparator = compareBy { it.id } +private fun > compareById(): Comparator = compareBy { it.id } -private fun > createDepthPathSelector(random: Random? = null): UPathSelector { +private fun > createDepthPathSelector(random: Random? = null): UPathSelector { if (random == null) { return WeightedPathSelector( priorityCollectionFactory = { DeterministicPriorityCollection(Comparator.naturalOrder()) }, @@ -111,33 +122,33 @@ private fun > createDepthPathSelector(random: ) } -private fun > createClosestToUncoveredPathSelector( +private fun > createClosestToUncoveredPathSelector( coverageStatistics: CoverageStatistics, distanceStatistics: DistanceStatistics, random: Random? = null, ): UPathSelector { - val weighter = ShortestDistanceToTargetsStateWeighter<_, _, State>( + val distanceCalculator = RoughIterprocShortestDistanceCalculator( targets = coverageStatistics.getUncoveredStatements(), getCfgDistance = distanceStatistics::getShortestCfgDistance, getCfgDistanceToExitPoint = distanceStatistics::getShortestCfgDistanceToExitPoint ) - coverageStatistics.addOnCoveredObserver { _, method, statement -> weighter.removeTarget(method, statement) } + coverageStatistics.addOnCoveredObserver { _, method, statement -> distanceCalculator.removeTarget(method, statement) } if (random == null) { return WeightedPathSelector( priorityCollectionFactory = { DeterministicPriorityCollection(Comparator.naturalOrder()) }, - weighter = weighter + weighter = { distanceCalculator.calculateDistance(it.currentStatement, it.callStack) } ) } return WeightedPathSelector( priorityCollectionFactory = { RandomizedPriorityCollection(compareById()) { random.nextDouble() } }, - weighter = { 1.0 / max(weighter.weight(it).toDouble(), 1.0) } + weighter = { 1.0 / max(distanceCalculator.calculateDistance(it.currentStatement, it.callStack).toDouble(), 1.0) } ) } -private fun > createForkDepthPathSelector( +private fun > createForkDepthPathSelector( random: Random? = null, ): UPathSelector { if (random == null) { @@ -152,3 +163,35 @@ private fun > weighter = { 1.0 / max(it.pathLocation.depth.toDouble(), 1.0) } ) } + +internal fun , State : UState<*, *, Method, Statement, *, Target, State>> createClosestToTargetsPathSelector( + distanceStatistics: DistanceStatistics, + random: Random? = null, +): UPathSelector { + val distanceCalculator = DynamicTargetsShortestDistanceCalculator { m, s -> + RoughIterprocShortestDistanceCalculator( + targets = listOf(m to s), + getCfgDistance = distanceStatistics::getShortestCfgDistance, + getCfgDistanceToExitPoint = distanceStatistics::getShortestCfgDistanceToExitPoint + ) + } + + fun calculateDistanceToTargets(state: State) = distanceCalculator.calculateDistance( + state.currentStatement, + state.callStack, + state.targets.map { it.location }.toList(), + folder = { if (it.isNotEmpty()) it.min() else UInt.MAX_VALUE } + ) + + if (random == null) { + return WeightedPathSelector( + priorityCollectionFactory = { DeterministicPriorityCollection(Comparator.naturalOrder()) }, + weighter = ::calculateDistanceToTargets + ) + } + + return WeightedPathSelector( + priorityCollectionFactory = { RandomizedPriorityCollection(compareById()) { random.nextDouble() } }, + weighter = { 1.0 / max(calculateDistanceToTargets(it).toDouble(), 1.0) } + ) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/RandomTreePathSelector.kt b/usvm-core/src/main/kotlin/org/usvm/ps/RandomTreePathSelector.kt index 3883ba5776..8294135dfb 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/RandomTreePathSelector.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/RandomTreePathSelector.kt @@ -20,7 +20,7 @@ import java.util.IdentityHashMap * @param randomNonNegativeInt function returning non negative random integer used to select the next child in tree. * @param ignoreToken token to visit only the subtree of not removed states. Should be different for different consumers. */ -internal class RandomTreePathSelector, Statement>( +internal class RandomTreePathSelector, Statement>( private val root: PathsTrieNode, private val randomNonNegativeInt: () -> Int, private val ignoreToken: Long = 0, diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt index 4060623a35..443cc1bd98 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt @@ -13,7 +13,7 @@ import java.util.concurrent.ConcurrentHashMap * @param methods methods to track coverage of. * @param applicationGraph [ApplicationGraph] used to retrieve statements by method. */ -class CoverageStatistics>( +class CoverageStatistics>( methods: Set, private val applicationGraph: ApplicationGraph ) : UMachineObserver { diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/TargetsReachedStatesCollector.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/TargetsReachedStatesCollector.kt new file mode 100644 index 0000000000..e9e7413b62 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/TargetsReachedStatesCollector.kt @@ -0,0 +1,14 @@ +package org.usvm.statistics + +import org.usvm.UState + +class TargetsReachedStatesCollector> : UMachineObserver { + private val mutableCollectedStates = mutableListOf() + val collectedStates: List = mutableCollectedStates + + override fun onStateTerminated(state: State) { + if (state.reachedSinks.isNotEmpty()) { + mutableCollectedStates.add(state) + } + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/TerminatedStateRemover.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/TerminatedStateRemover.kt index c45cb53aa8..9364e9ac01 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/TerminatedStateRemover.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/TerminatedStateRemover.kt @@ -10,7 +10,7 @@ import org.usvm.UState * it won't remove terminated states from the path trie. * It costs additional memory, but might be useful for debug purposes. */ -class TerminatedStateRemover> : UMachineObserver { +class TerminatedStateRemover> : UMachineObserver { override fun onStateTerminated(state: State, stateReachable: Boolean) { state.pathLocation.states.remove(state) } diff --git a/usvm-core/src/test/kotlin/org/usvm/PathTests.kt b/usvm-core/src/test/kotlin/org/usvm/PathTests.kt index 99f852e3b4..39f0a6d5e3 100644 --- a/usvm-core/src/test/kotlin/org/usvm/PathTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/PathTests.kt @@ -11,12 +11,12 @@ class PathTests { val initialState = mockk() val fork = mockk() - val root = RootNode() + val root = RootNode() - val firstRealNode = root.pathLocationFor(1, initialState) + val firstRealNode = root.pathLocationFor(TestInstruction(1), initialState) - val updatedInitialState = firstRealNode.pathLocationFor(statement = 2, initialState) - val forkedState = firstRealNode.pathLocationFor(statement = 3, fork) + val updatedInitialState = firstRealNode.pathLocationFor(statement = TestInstruction(2), initialState) + val forkedState = firstRealNode.pathLocationFor(statement = TestInstruction(3), fork) assertTrue { root.children.size == 1 } assertTrue { root.children.values.single() == firstRealNode } @@ -36,16 +36,16 @@ class PathTests { val firstState = mockk() val secondState = mockk() - val root = RootNode() + val root = RootNode() - val firstRealNode = root.pathLocationFor(1, firstState) + val firstRealNode = root.pathLocationFor(TestInstruction(1), firstState) - val updatedFirstState = firstRealNode.pathLocationFor(statement = 2, firstState) - val updatedSecondState = firstRealNode.pathLocationFor(statement = 2, secondState) + val updatedFirstState = firstRealNode.pathLocationFor(statement = TestInstruction(2), firstState) + val updatedSecondState = firstRealNode.pathLocationFor(statement = TestInstruction(2), secondState) assertSame(updatedFirstState, updatedSecondState) assertTrue { updatedFirstState.children.isEmpty() } assertTrue { updatedFirstState.states.size == 2 } } -} \ No newline at end of file +} diff --git a/usvm-core/src/test/kotlin/org/usvm/TestApplicationGraph.kt b/usvm-core/src/test/kotlin/org/usvm/TestApplicationGraph.kt new file mode 100644 index 0000000000..4f91c59a3b --- /dev/null +++ b/usvm-core/src/test/kotlin/org/usvm/TestApplicationGraph.kt @@ -0,0 +1,115 @@ +package org.usvm + +import org.usvm.statistics.ApplicationGraph + +internal data class TestInstruction(val method: String, val offset: Int) { + constructor(offset: Int) : this("", offset) +} + +internal interface TestMethodGraphBuilder { + fun entryPoint(offset: Int) + fun exitPoint(offset: Int) + fun edge(from: Int, to: Int) + fun bidirectionalEdge(from: Int, to: Int) + fun call(offset: Int, callee: String) +} + +internal interface TestApplicationGraphBuilder { + fun method(name: String, instructionsCount: Int, init: TestMethodGraphBuilder.() -> Unit) +} + +private class TestMethodGraphBuilderImpl(val name: String, instructionsCount: Int) : TestMethodGraphBuilder { + val adjacencyLists = Array(instructionsCount) { mutableSetOf(it) } + val calleesByOffset = mutableMapOf() + val offsetsByCallee = mutableMapOf>() + val entryPoints = mutableSetOf() + val exitPoints = mutableSetOf() + + override fun entryPoint(offset: Int) { + entryPoints.add(offset) + } + + override fun exitPoint(offset: Int) { + exitPoints.add(offset) + } + + override fun edge(from: Int, to: Int) { + adjacencyLists[from].add(to) + } + + override fun bidirectionalEdge(from: Int, to: Int) { + adjacencyLists[from].add(to) + adjacencyLists[to].add(from) + } + + override fun call(offset: Int, callee: String) { + calleesByOffset[offset] = callee + offsetsByCallee.computeIfAbsent(callee) { mutableListOf() }.add(offset) + } +} + +private class TestApplicationGraphBuilderImpl : TestApplicationGraphBuilder, ApplicationGraph { + private val methodBuilders = mutableMapOf() + + override fun method(name: String, instructionsCount: Int, init: TestMethodGraphBuilder.() -> Unit) { + val builder = TestMethodGraphBuilderImpl(name, instructionsCount) + init(builder) + methodBuilders[name] = builder + } + + override fun predecessors(node: TestInstruction): Sequence { + val builder = methodBuilders.getValue(node.method) + val predecessors = mutableListOf() + for (i in builder.adjacencyLists.indices) { + if (builder.adjacencyLists[i].contains(node.offset)) { + predecessors.add(TestInstruction(node.method, i)) + } + } + return predecessors.asSequence() + } + + override fun successors(node: TestInstruction): Sequence { + return methodBuilders + .getValue(node.method) + .adjacencyLists[node.offset] + .map { TestInstruction(node.method, it) } + .asSequence() + } + + override fun callees(node: TestInstruction): Sequence { + val builder = methodBuilders.getValue(node.method) + return builder.calleesByOffset[node.offset]?.let { sequenceOf(it) } ?: emptySequence() + } + + override fun callers(method: String): Sequence { + return methodBuilders + .mapNotNull { m -> m.value.offsetsByCallee[method]?.map { TestInstruction(m.value.name, it) } } + .flatten() + .asSequence() + } + + override fun entryPoints(method: String): Sequence { + return methodBuilders.getValue(method).entryPoints.map { TestInstruction(method, it) }.asSequence() + } + + override fun exitPoints(method: String): Sequence { + return methodBuilders.getValue(method).exitPoints.map { TestInstruction(method, it) }.asSequence() + } + + override fun methodOf(node: TestInstruction): String = node.method + + override fun statementsOf(method: String): Sequence { + return methodBuilders + .getValue(method) + .adjacencyLists + .indices + .map { TestInstruction(method, it) } + .asSequence() + } +} + +internal fun appGraph(init: TestApplicationGraphBuilder.() -> Unit): ApplicationGraph { + val builder = TestApplicationGraphBuilderImpl() + init(builder) + return builder +} diff --git a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt index 89588ec6cf..ac45fe9278 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt @@ -1,5 +1,8 @@ package org.usvm +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk import org.usvm.constraints.UPathConstraints import org.usvm.memory.UMemory import org.usvm.memory.USymbolicCollectionKeyInfo @@ -26,12 +29,20 @@ internal fun pseudoRandom(i: Int): Int { return res } +internal class TestTarget(method: String, offset: Int) : UTarget(method, TestInstruction(method, offset)) { + fun reach(state: TestState) { + visit(state) + } +} + internal class TestState( ctx: UContext, - callStack: UCallStack, pathConstraints: UPathConstraints, - memory: UMemory, models: List>, - pathLocation: PathsTrieNode, -) : UState(ctx, callStack, pathConstraints, memory, models, pathLocation) { + callStack: UCallStack, pathConstraints: UPathConstraints, + memory: UMemoryBase, models: List>, + pathLocation: PathsTrieNode, + targetTrees: List = emptyList() +) : UState(ctx, callStack, pathConstraints, memory, models, pathLocation, targetTrees) { + override fun clone(newConstraints: UPathConstraints?): TestState = this override val isExceptional = false @@ -52,3 +63,11 @@ interface TestKeyInfo> : USymbolicCollectionKeyInfo override fun topRegion(): Reg = shouldNotBeCalled() override fun bottomRegion(): Reg = shouldNotBeCalled() } + +internal fun mockState(id: StateId, currentLocation: TestInstruction, callStack: UCallStack, targets: List): TestState { + val ctxMock = mockk() + every { ctxMock.getNextStateId() } returns id + val spyk = spyk(TestState(ctxMock, callStack, mockk(), mockk(), emptyList(), mockk(), targets)) + every { spyk.currentStatement } returns currentLocation + return spyk +} diff --git a/usvm-core/src/test/kotlin/org/usvm/ps/ClosestToTargetsPathSelectorIntegrationTests.kt b/usvm-core/src/test/kotlin/org/usvm/ps/ClosestToTargetsPathSelectorIntegrationTests.kt new file mode 100644 index 0000000000..eed5d4a6a8 --- /dev/null +++ b/usvm-core/src/test/kotlin/org/usvm/ps/ClosestToTargetsPathSelectorIntegrationTests.kt @@ -0,0 +1,73 @@ +package org.usvm.ps + +import io.mockk.every +import org.junit.jupiter.api.Test +import org.usvm.* +import org.usvm.TestInstruction +import org.usvm.appGraph +import org.usvm.statistics.DistanceStatistics +import kotlin.test.assertEquals + +internal class ClosestToTargetsPathSelectorIntegrationTests { + + private val appGraph1 = appGraph { + method("A", 8) { + entryPoint(0) + edge(0, 1) + edge(0, 2) + edge(1, 7) + edge(7, 3) + edge(2, 4) + edge(4, 5) + edge(5, 6) + edge(6, 3) + exitPoint(3) + } + } + + @Test + fun `Smoke test, single target and two states, one is closer`() { + val pathSelector = createClosestToTargetsPathSelector(DistanceStatistics(appGraph1)) + val target = TestTarget("A", 3) + + val state1 = mockState(1u, TestInstruction("A", 1), UCallStack("A"), listOf(target)) + val state2 = mockState(2u, TestInstruction("A", 2), UCallStack("A"), listOf(target)) + + pathSelector.add(listOf(state1, state2)) + assertEquals(state1, pathSelector.peek()) + } + + @Test + fun `Multiple targets smoke test`() { + val pathSelector = createClosestToTargetsPathSelector(DistanceStatistics(appGraph1)) + val target = TestTarget("A", 4).apply { + addChild(TestTarget("A", 3)) + } + + val state1 = mockState(1u, TestInstruction("A", 1), UCallStack("A"), listOf(target)) + val state2 = mockState(2u, TestInstruction("A", 2), UCallStack("A"), listOf(target)) + + pathSelector.add(listOf(state1, state2)) + assertEquals(state2, pathSelector.peek()) + } + + @Test + fun `State steps to target and becomes not closest`() { + val pathSelector = createClosestToTargetsPathSelector(DistanceStatistics(appGraph1)) + val target1 = TestTarget("A", 3) + + val target2 = TestTarget("A", 4).apply { + addChild(TestTarget("A", 3)) + } + + val state1 = mockState(1u, TestInstruction("A", 1), UCallStack("A"), listOf(target1)) + val state2 = mockState(2u, TestInstruction("A", 2), UCallStack("A"), listOf(target2)) + + pathSelector.add(listOf(state1, state2)) + assertEquals(state2, pathSelector.peek()) + target2.reach(state2) + every { state2.currentStatement } returns TestInstruction("A", 4) + pathSelector.update(state2) + assertEquals(state1, pathSelector.peek()) + } +} diff --git a/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt b/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt index c4b5f2d76b..c2e75b6837 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt @@ -8,22 +8,19 @@ import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource +import org.usvm.* import org.usvm.TestState -import org.usvm.UState import org.usvm.pseudoRandom -import org.usvm.PathsTrieNode -import org.usvm.PathsTrieNodeImpl -import org.usvm.RootNode import kotlin.test.assertEquals internal class RandomTreePathSelectorTests { private class TreeBuilder( - prevNode: PathsTrieNode, + prevNode: PathsTrieNode, statement: Int, ) { val node = when (prevNode) { - is RootNode -> PathsTrieNodeImpl(prevNode, statement, staticState) - is PathsTrieNodeImpl -> PathsTrieNodeImpl(prevNode, statement, staticState) + is RootNode -> PathsTrieNodeImpl(prevNode, TestInstruction(statement), staticState) + is PathsTrieNodeImpl -> PathsTrieNodeImpl(prevNode, TestInstruction(statement), staticState) } fun child(init: TreeBuilder.() -> Unit) { @@ -36,7 +33,7 @@ internal class RandomTreePathSelectorTests { val stmt = nextStatement() with(state) { - pathLocation = node.pathLocationFor(stmt, this) + pathLocation = node.pathLocationFor(TestInstruction(stmt), this) } } @@ -51,10 +48,10 @@ internal class RandomTreePathSelectorTests { @Test fun smokeTest() { - val rootNode = RootNode() + val rootNode = RootNode() val state1 = mockk() - every { state1.pathLocation } returns PathsTrieNodeImpl(rootNode, statement = 1, state1) + every { state1.pathLocation } returns PathsTrieNodeImpl(rootNode, statement = TestInstruction(1), state1) val selector = RandomTreePathSelector(rootNode, { 0 }, 0L) @@ -64,10 +61,10 @@ internal class RandomTreePathSelectorTests { @Test fun peekFromEmptySelectorAndNonEmptyPathsTreeTest() { - val rootNode = RootNode() + val rootNode = RootNode() val state1 = mockk() - every { state1.pathLocation } returns PathsTrieNodeImpl(rootNode, statement = 1, state1) + every { state1.pathLocation } returns PathsTrieNodeImpl(rootNode, statement = TestInstruction(1), state1) val selector = RandomTreePathSelector(rootNode, { 0 }, 0L) @@ -77,7 +74,7 @@ internal class RandomTreePathSelectorTests { @ParameterizedTest @MethodSource("testCases") fun regularPeekTest( - root: PathsTrieNode, + root: PathsTrieNode, states: List, randomChoices: List, expectedStates: List, @@ -173,7 +170,7 @@ internal class RandomTreePathSelectorTests { } companion object { - private fun , Statement> registerLocationsInTree( + private fun , Statement> registerLocationsInTree( root: PathsTrieNode, selector: RandomTreePathSelector, ) { @@ -184,8 +181,8 @@ internal class RandomTreePathSelectorTests { } } - private fun tree(init: TreeBuilder.() -> Unit): PathsTrieNode { - val rootNode = RootNode() + private fun tree(init: TreeBuilder.() -> Unit): PathsTrieNode { + val rootNode = RootNode() val builder = TreeBuilder(rootNode, statement = TreeBuilder.nextStatement()) init(builder) return rootNode diff --git a/usvm-core/src/test/kotlin/org/usvm/ps/ShortestDistanceToTargetsStateWeighterTests.kt b/usvm-core/src/test/kotlin/org/usvm/ps/RoughIterprocShortestDistanceCalculatorTests.kt similarity index 55% rename from usvm-core/src/test/kotlin/org/usvm/ps/ShortestDistanceToTargetsStateWeighterTests.kt rename to usvm-core/src/test/kotlin/org/usvm/ps/RoughIterprocShortestDistanceCalculatorTests.kt index 97c92b289a..2652d2f8e0 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ps/ShortestDistanceToTargetsStateWeighterTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ps/RoughIterprocShortestDistanceCalculatorTests.kt @@ -1,14 +1,12 @@ package org.usvm.ps import io.mockk.every -import io.mockk.mockk import org.junit.jupiter.api.Test -import org.usvm.TestState import org.usvm.UCallStack import kotlin.test.assertEquals @OptIn(ExperimentalUnsignedTypes::class) -internal class ShortestDistanceToTargetsStateWeighterTests { +internal class RoughIterprocShortestDistanceCalculatorTests { private val methodAShortestDistanceMatrix = arrayOf( uintArrayOf(), @@ -56,27 +54,25 @@ internal class ShortestDistanceToTargetsStateWeighterTests { } } - val weighter = ShortestDistanceToTargetsStateWeighter<_, _, TestState>( + val calculator = RoughIterprocShortestDistanceCalculator( setOf("A" to 2, "A" to 3, "A" to 4, "A" to 5, "A" to 6), ::getCfgDistance ) { _, _ -> 1u } - val mockState = mockk() - every { mockState.currentStatement } returns 1 + val currentStatement = 1 val callStack = UCallStack("A") - every { mockState.callStack } returns callStack - - assertEquals(1u, weighter.weight(mockState)) - weighter.removeTarget("A", 2) - assertEquals(1u, weighter.weight(mockState)) - weighter.removeTarget("A", 4) - assertEquals(2u, weighter.weight(mockState)) - weighter.removeTarget("A", 3) - assertEquals(3u, weighter.weight(mockState)) - weighter.removeTarget("A", 5) - assertEquals(7u, weighter.weight(mockState)) - weighter.removeTarget("A", 6) - assertEquals(UInt.MAX_VALUE, weighter.weight(mockState)) + + assertEquals(1u, calculator.calculateDistance(currentStatement, callStack)) + calculator.removeTarget("A", 2) + assertEquals(1u, calculator.calculateDistance(currentStatement, callStack)) + calculator.removeTarget("A", 4) + assertEquals(2u, calculator.calculateDistance(currentStatement, callStack)) + calculator.removeTarget("A", 3) + assertEquals(3u, calculator.calculateDistance(currentStatement, callStack)) + calculator.removeTarget("A", 5) + assertEquals(7u, calculator.calculateDistance(currentStatement, callStack)) + calculator.removeTarget("A", 6) + assertEquals(UInt.MAX_VALUE, calculator.calculateDistance(currentStatement, callStack)) } @Test @@ -89,45 +85,43 @@ internal class ShortestDistanceToTargetsStateWeighterTests { return shortestDistances.getValue(method)[from][0] } - val mockState = mockk() + var currentStatment = 3 val callStack = UCallStack("A") callStack.push("B", 3) callStack.push("C", 2) - every { mockState.currentStatement } returns 3 - every { mockState.callStack } returns callStack - val weighter = - ShortestDistanceToTargetsStateWeighter<_, _, TestState>(setOf("C" to 4), ::getCfgDistance, ::getCfgDistanceToExitPoint) - assertEquals(10u, weighter.weight(mockState)) + val calculator = + RoughIterprocShortestDistanceCalculator(setOf("C" to 4), ::getCfgDistance, ::getCfgDistanceToExitPoint) + assertEquals(10u, calculator.calculateDistance(currentStatment, callStack)) - weighter.removeTarget("C", 4) - weighter.addTarget("A", 2) - assertEquals(7u, weighter.weight(mockState)) + calculator.removeTarget("C", 4) + calculator.addTarget("A", 2) + assertEquals(7u, calculator.calculateDistance(currentStatment, callStack)) - weighter.addTarget("C", 4) - assertEquals(7u, weighter.weight(mockState)) + calculator.addTarget("C", 4) + assertEquals(7u, calculator.calculateDistance(currentStatment, callStack)) - weighter.addTarget("B", 3) - assertEquals(5u, weighter.weight(mockState)) + calculator.addTarget("B", 3) + assertEquals(5u, calculator.calculateDistance(currentStatment, callStack)) - weighter.addTarget("C", 1) - assertEquals(3u, weighter.weight(mockState)) + calculator.addTarget("C", 1) + assertEquals(3u, calculator.calculateDistance(currentStatment, callStack)) callStack.pop() - every { mockState.currentStatement } returns 5 - assertEquals(UInt.MAX_VALUE, weighter.weight(mockState)) + currentStatment = 5 + assertEquals(UInt.MAX_VALUE, calculator.calculateDistance(currentStatment, callStack)) callStack.pop() - every { mockState.currentStatement } returns 2 - assertEquals(0u, weighter.weight(mockState)) + currentStatment = 2 + assertEquals(0u, calculator.calculateDistance(currentStatment, callStack)) - weighter.removeTarget("A", 2) - assertEquals(UInt.MAX_VALUE, weighter.weight(mockState)) + calculator.removeTarget("A", 2) + assertEquals(UInt.MAX_VALUE, calculator.calculateDistance(currentStatment, callStack)) callStack.push("C", 1) callStack.push("C", 1) - every { mockState.currentStatement } returns 2 - assertEquals(2u, weighter.weight(mockState)) + currentStatment = 2 + assertEquals(2u, calculator.calculateDistance(currentStatment, callStack)) } } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt new file mode 100644 index 0000000000..7ef6c54502 --- /dev/null +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt @@ -0,0 +1,20 @@ +package org.usvm.api.targets + +import org.jacodb.api.JcMethod +import org.jacodb.api.cfg.JcInst +import org.usvm.machine.state.JcState + +class JcLocationTarget(method: JcMethod, inst: JcInst) : JcTarget(method, inst) { + + private fun hasReached(state: JcState): Boolean { + return state.callStack.isNotEmpty() && method == state.callStack.lastMethod() && statement == state.currentStatement + } + + override fun onState(parent: JcState, forks: Sequence) { + val reachedState = if (hasReached(parent)) parent else forks.find(::hasReached) + if (reachedState != null) { + visit(reachedState) + } + super.onState(parent, forks) + } +} diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt new file mode 100644 index 0000000000..5633f640e3 --- /dev/null +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt @@ -0,0 +1,16 @@ +package org.usvm.api.targets + +import org.jacodb.api.JcMethod +import org.jacodb.api.cfg.JcInst +import org.usvm.UHeapRef +import org.usvm.machine.state.JcState + +class JcNullPointerDereferenceTarget(method: JcMethod, inst: JcInst) : JcTarget(method, inst) { + + override fun onNullPointerDereference(state: JcState, ref: UHeapRef) { + if (state.callStack.lastMethod() == method && state.currentStatement == statement) { + visit(state) + } + super.onNullPointerDereference(state, ref) + } +} diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt new file mode 100644 index 0000000000..4c9e1d3599 --- /dev/null +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt @@ -0,0 +1,15 @@ +package org.usvm.api.targets + +import org.jacodb.api.JcMethod +import org.jacodb.api.cfg.JcInst +import org.usvm.UHeapRef +import org.usvm.UTarget +import org.usvm.machine.interpreter.JcInterpreterObserver +import org.usvm.machine.state.JcState + +open class JcTarget(method: JcMethod, inst: JcInst) : UTarget(method, inst), JcInterpreterObserver { + + override fun onNullPointerDereference(state: JcState, ref: UHeapRef) { + forEachChild { onNullPointerDereference(state, ref) } + } +} diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcContext.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcContext.kt index b3e3c3eda9..d3e7a2bcc8 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcContext.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcContext.kt @@ -22,12 +22,15 @@ import org.jacodb.api.ext.void import org.jacodb.impl.bytecode.JcFieldImpl import org.jacodb.impl.types.FieldInfo import org.usvm.UContext +import org.usvm.machine.interpreter.CompositeJcInterpreterObserver import org.usvm.util.extractJcRefType class JcContext( val cp: JcClasspath, components: JcComponents, ) : UContext(components) { + val jcInterpreterObservers = CompositeJcInterpreterObserver() + val voidSort by lazy { JcVoidSort(this) } val longSort get() = bv64Sort diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt index 7eb44a2e4d..d96a7c0f18 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt @@ -5,21 +5,15 @@ import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst import org.jacodb.api.ext.methods -import org.usvm.CoverageZone -import org.usvm.UMachine -import org.usvm.UMachineOptions +import org.usvm.* +import org.usvm.api.targets.JcTarget import org.usvm.machine.interpreter.JcInterpreter import org.usvm.machine.state.JcMethodResult import org.usvm.machine.state.JcState import org.usvm.machine.state.lastStmt import org.usvm.ps.createPathSelector -import org.usvm.statistics.CompositeUMachineObserver -import org.usvm.statistics.CoverageStatistics -import org.usvm.statistics.CoveredNewStatesCollector -import org.usvm.statistics.DistanceStatistics -import org.usvm.statistics.TerminatedStateRemover -import org.usvm.statistics.TransitiveCoverageZoneObserver -import org.usvm.statistics.UMachineObserver +import org.usvm.ps.createTargetReproductionPathSelector +import org.usvm.statistics.* import org.usvm.stopstrategies.createStopStrategy val logger = object : KLogging() {}.logger @@ -38,9 +32,7 @@ class JcMachine( private val distanceStatistics = DistanceStatistics(applicationGraph) - fun analyze( - method: JcMethod - ): List { + fun analyze(method: JcMethod): List { logger.debug("$this.analyze($method)") val initialState = interpreter.getInitialState(method) @@ -102,6 +94,37 @@ class JcMachine( return statesCollector.collectedStates } + fun reproduceTargets( + method: JcMethod, + targets: List, + options: TargetReproductionOptions = TargetReproductionOptions() + ): List { + logger.debug("$this.reproduceTargets($method)") + val initialState = interpreter.getInitialState(method, targets) + val pathSelector = createTargetReproductionPathSelector(initialState, options, distanceStatistics) + + // TODO: gracefully remove + targets.forEach { ctx.jcInterpreterObservers += it } + + val statesCollector = TargetsReachedStatesCollector() + val observers = mutableListOf( + statesCollector, + TerminatedStateRemover() + ) + observers.addAll(targets) + + run( + interpreter, + pathSelector, + observer = CompositeUMachineObserver(observers), + isStateTerminated = ::isStateTerminated, + // TODO: configure + stopStrategy = { false }, + ) + + return statesCollector.collectedStates + } + private fun isStateTerminated(state: JcState): Boolean { return state.callStack.isEmpty() } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt index 76c649e1db..cbbd7dbdd3 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt @@ -689,7 +689,10 @@ class JcExprResolver( val neqNull = mkHeapRefEq(ref, nullRef).not() scope.fork( neqNull, - blockOnFalseState = allocateException(nullPointerExceptionType) + blockOnFalseState = { + ctx.jcInterpreterObservers.onNullPointerDereference(this, ref) + allocateException(nullPointerExceptionType)(this) + } ) } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt index 7edf768165..d776d6bfb3 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt @@ -39,6 +39,7 @@ import org.usvm.UBoolExpr import org.usvm.UConcreteHeapRef import org.usvm.UInterpreter import org.usvm.api.allocate +import org.usvm.api.targets.JcTarget import org.usvm.api.typeStreamOf import org.usvm.machine.JcApplicationGraph import org.usvm.machine.JcContext @@ -79,8 +80,8 @@ class JcInterpreter( val logger = object : KLogging() {}.logger } - fun getInitialState(method: JcMethod): JcState { - val state = JcState(ctx) + fun getInitialState(method: JcMethod, targets: List = emptyList()): JcState { + val state = JcState(ctx, targets = targets) state.newStmt(JcMethodEntrypointInst(method)) val typedMethod = with(applicationGraph) { method.typed } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreterObserver.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreterObserver.kt new file mode 100644 index 0000000000..ee8a65254d --- /dev/null +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreterObserver.kt @@ -0,0 +1,27 @@ +package org.usvm.machine.interpreter + +import org.usvm.UHeapRef +import org.usvm.machine.state.JcState +import org.usvm.statistics.UMachineObserver + +interface JcInterpreterObserver { + fun onNullPointerDereference(state: JcState, ref: UHeapRef) { } +} + +class CompositeJcInterpreterObserver(observers: List) : JcInterpreterObserver { + private val observers = observers.toMutableList() + + constructor(vararg observers: JcInterpreterObserver) : this(observers.toMutableList()) + + override fun onNullPointerDereference(state: JcState, ref: UHeapRef) { + observers.forEach { it.onNullPointerDereference(state, ref) } + } + + operator fun plusAssign(observer: JcInterpreterObserver) { + observers.add(observer) + } + + operator fun minusAssign(observer: JcInterpreterObserver) { + observers.remove(observer) + } +} diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt index 9df90e15f8..5c674f06e5 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt @@ -10,6 +10,8 @@ import org.usvm.constraints.UPathConstraints import org.usvm.machine.JcContext import org.usvm.memory.UMemory import org.usvm.model.UModelBase +import org.usvm.PathsTrieNode +import org.usvm.api.targets.JcTarget class JcState( ctx: JcContext, @@ -19,13 +21,15 @@ class JcState( models: List> = listOf(), override var pathLocation: PathsTrieNode = ctx.mkInitialLocation(), var methodResult: JcMethodResult = JcMethodResult.NoCall, -) : UState( + targets: List = emptyList() +) : UState( ctx, callStack, pathConstraints, memory, models, - pathLocation + pathLocation, + targets ) { override fun clone(newConstraints: UPathConstraints?): JcState { val clonedConstraints = newConstraints ?: pathConstraints.clone() @@ -37,6 +41,7 @@ class JcState( models, pathLocation, methodResult, + targets.toList() ) } diff --git a/usvm-jvm/src/samples/java/org/usvm/samples/targets/NullPointerDereference.java b/usvm-jvm/src/samples/java/org/usvm/samples/targets/NullPointerDereference.java new file mode 100644 index 0000000000..aee8e776ed --- /dev/null +++ b/usvm-jvm/src/samples/java/org/usvm/samples/targets/NullPointerDereference.java @@ -0,0 +1,20 @@ +package org.usvm.samples.targets; + +class ClassWithValue { + public int value; +} + +public class NullPointerDereference { + + public static int twoPathsToNPE(ClassWithValue obj, int n) { + int m = 0; + + if (n > 100) { + m += n; + } else { + m -= n; + } + + return obj.value + m; + } +} diff --git a/usvm-jvm/src/test/kotlin/org/usvm/samples/targets/TestNullPointerDereference.kt b/usvm-jvm/src/test/kotlin/org/usvm/samples/targets/TestNullPointerDereference.kt new file mode 100644 index 0000000000..7628a12e22 --- /dev/null +++ b/usvm-jvm/src/test/kotlin/org/usvm/samples/targets/TestNullPointerDereference.kt @@ -0,0 +1,38 @@ +package org.usvm.samples.targets + +import io.mockk.internalSubstitute +import org.jacodb.api.ext.findClass +import org.jacodb.api.ext.toType +import org.junit.jupiter.api.Test +import org.usvm.UMachineOptions +import org.usvm.api.targets.JcLocationTarget +import org.usvm.api.targets.JcNullPointerDereferenceTarget +import org.usvm.machine.JcMachine +import org.usvm.samples.JacoDBContainer +import org.usvm.samples.samplesKey +import kotlin.reflect.jvm.javaMethod +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class TestNullPointerDereference { + + @Test + fun `Null Pointer Dereference with intermediate target`() { + val cp = JacoDBContainer(samplesKey).cp + val declaringClassName = requireNotNull(NullPointerDereference::twoPathsToNPE.javaMethod?.declaringClass?.name) + val jcClass = cp.findClass(declaringClassName).toType() + val jcMethod = jcClass.declaredMethods.first { it.name == NullPointerDereference::twoPathsToNPE.name }.method + + val target = JcLocationTarget(jcMethod, jcMethod.instList[5]).apply { + addChild(JcNullPointerDereferenceTarget(jcMethod, jcMethod.instList[8])) + } + + val states = JcMachine(cp, UMachineOptions()).use { machine -> + machine.reproduceTargets(jcMethod, listOf(target)) + } + assertEquals(1, states.size) + assertNotNull(states.single().reversedPath.asSequence().singleOrNull { it == jcMethod.instList[8] }) + assertNotNull(states.single().reversedPath.asSequence().singleOrNull { it == jcMethod.instList[5] }) + } +} diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleState.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleState.kt index 64f081ccf3..d99f54d260 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleState.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleState.kt @@ -25,13 +25,15 @@ class SampleState( pathLocation: PathsTrieNode = ctx.mkInitialLocation(), var returnRegister: UExpr? = null, var exceptionRegister: ProgramException? = null, -) : UState, Stmt, UContext, SampleState>( + targets: List = emptyList() +) : UState, Stmt, UContext, SampleTarget, SampleState>( ctx, callStack, pathConstraints, memory, models, - pathLocation + pathLocation, + targets ) { override fun clone(newConstraints: UPathConstraints?): SampleState { val clonedConstraints = newConstraints ?: pathConstraints.clone() @@ -43,7 +45,8 @@ class SampleState( models, pathLocation, returnRegister, - exceptionRegister + exceptionRegister, + targets.toList() ) } diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt new file mode 100644 index 0000000000..014736f1cf --- /dev/null +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt @@ -0,0 +1,7 @@ +package org.usvm.machine + +import org.usvm.UTarget +import org.usvm.language.Method +import org.usvm.language.Stmt + +open class SampleTarget(method: Method<*>, statement: Stmt) : UTarget, Stmt, SampleTarget, SampleState>(method, statement) diff --git a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt index 9731bc2502..e8ef6db352 100644 --- a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt +++ b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt @@ -55,6 +55,11 @@ enum class PathSelectionStrategy { CLOSEST_TO_UNCOVERED_RANDOM } +enum class TargetReproductionPathSelectionStrategy { + DETERMINISTIC, + RANDOMIZED +} + enum class PathSelectorCombinationStrategy { /** * Multiple path selectors have the common state set and are interleaved. @@ -136,3 +141,19 @@ data class UMachineOptions( */ val solverType: SolverType = SolverType.Z3 ) + +data class TargetReproductionOptions( + val pathSelectionStrategy: TargetReproductionPathSelectionStrategy = TargetReproductionPathSelectionStrategy.RANDOMIZED, + /** + * Seed used for random operations. + */ + val randomSeed: Long = 0, + /** + * Optional limit of symbolic execution steps to stop execution on. + */ + val stepLimit: ULong? = null, + /** + * Optional timeout in milliseconds to stop execution on. + */ + val timeoutMs: Long? = 20_000, +) From 786ce87bd3e5ba62790b0a88a2f27fc55b95481c Mon Sep 17 00:00:00 2001 From: Maksim Parshin Date: Wed, 30 Aug 2023 15:10:44 +0300 Subject: [PATCH 02/17] Add interproc distance calculator --- usvm-core/src/main/kotlin/org/usvm/State.kt | 2 +- .../kotlin/org/usvm/ps/PathSelectorFactory.kt | 73 +++- .../CallGraphReachabilityStatistics.kt | 20 ++ .../distances/CallStackDistanceCalculator.kt} | 42 +-- .../distances/DistanceCalculator.kt | 27 ++ .../{ => distances}/DistanceStatistics.kt | 3 +- .../distances/InterprocDistanceCalculator.kt | 89 +++++ .../stopstrategies/StepLimitStopStrategy.kt | 2 +- .../src/test/kotlin/org/usvm/PathTests.kt | 12 +- .../kotlin/org/usvm/TestApplicationGraph.kt | 6 +- .../src/test/kotlin/org/usvm/TestUtil.kt | 17 +- ...stToTargetsPathSelectorIntegrationTests.kt | 73 ---- .../usvm/ps/RandomTreePathSelectorTests.kt | 12 +- .../CallStackDistanceCalculatorTests.kt} | 10 +- .../InterprocDistanceCalculatorTests.kt | 332 ++++++++++++++++++ .../main/kotlin/org/usvm/machine/JcMachine.kt | 6 +- .../kotlin/org/usvm/machine/state/JcState.kt | 4 +- .../kotlin/org/usvm/machine/SampleMachine.kt | 8 +- .../kotlin/org/usvm/algorithms/GraphUtils.kt | 26 ++ .../main/kotlin/org/usvm/util/MathUtils.kt | 19 + .../org/usvm/algorithms/GraphUtilsTests.kt | 120 ------- 21 files changed, 625 insertions(+), 278 deletions(-) create mode 100644 usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphReachabilityStatistics.kt rename usvm-core/src/main/kotlin/org/usvm/{ps/DistanceCalculator.kt => statistics/distances/CallStackDistanceCalculator.kt} (69%) create mode 100644 usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt rename usvm-core/src/main/kotlin/org/usvm/statistics/{ => distances}/DistanceStatistics.kt (95%) create mode 100644 usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt delete mode 100644 usvm-core/src/test/kotlin/org/usvm/ps/ClosestToTargetsPathSelectorIntegrationTests.kt rename usvm-core/src/test/kotlin/org/usvm/{ps/RoughIterprocShortestDistanceCalculatorTests.kt => statistics/CallStackDistanceCalculatorTests.kt} (93%) create mode 100644 usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt create mode 100644 usvm-util/src/main/kotlin/org/usvm/util/MathUtils.kt delete mode 100644 usvm-util/src/test/kotlin/org/usvm/algorithms/GraphUtilsTests.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/State.kt b/usvm-core/src/main/kotlin/org/usvm/State.kt index 5090df9351..7d2b147e16 100644 --- a/usvm-core/src/main/kotlin/org/usvm/State.kt +++ b/usvm-core/src/main/kotlin/org/usvm/State.kt @@ -18,7 +18,7 @@ abstract class UState, open var models: List>, open var pathLocation: PathsTrieNode, - targets: List = emptyList() + targets: Collection = emptyList() ) { /** * Deterministic state id. diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt index 2e53a4cc92..f25f7322ee 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt @@ -1,10 +1,10 @@ package org.usvm.ps -import org.usvm.* +import org.usvm.statistics.ApplicationGraph import org.usvm.statistics.CoverageStatistics -import org.usvm.statistics.DistanceStatistics import org.usvm.algorithms.DeterministicPriorityCollection import org.usvm.algorithms.RandomizedPriorityCollection +import org.usvm.util.log2 import kotlin.math.max import kotlin.random.Random @@ -90,7 +90,7 @@ fun , Stat TargetReproductionPathSelectionStrategy.RANDOMIZED -> Random(options.randomSeed) TargetReproductionPathSelectionStrategy.DETERMINISTIC -> null } - val selector = createClosestToTargetsPathSelector(distanceStatistics, random) + val selector = createTargetedPathSelector(distanceStatistics, random) selector.add(listOf(initialState)) return selector } @@ -127,7 +127,7 @@ private fun , random: Random? = null, ): UPathSelector { - val distanceCalculator = RoughIterprocShortestDistanceCalculator( + val distanceCalculator = CallStackDistanceCalculator( targets = coverageStatistics.getUncoveredStatements(), getCfgDistance = distanceStatistics::getShortestCfgDistance, getCfgDistanceToExitPoint = distanceStatistics::getShortestCfgDistanceToExitPoint @@ -164,24 +164,26 @@ private fun , State : UState<*, *, Method, Statement, *, Target, State>> createClosestToTargetsPathSelector( +internal fun , State : UState<*, *, Method, Statement, *, Target, State>> createTargetedPathSelector( distanceStatistics: DistanceStatistics, random: Random? = null, ): UPathSelector { val distanceCalculator = DynamicTargetsShortestDistanceCalculator { m, s -> - RoughIterprocShortestDistanceCalculator( + CallStackDistanceCalculator( targets = listOf(m to s), getCfgDistance = distanceStatistics::getShortestCfgDistance, getCfgDistanceToExitPoint = distanceStatistics::getShortestCfgDistanceToExitPoint ) } - fun calculateDistanceToTargets(state: State) = distanceCalculator.calculateDistance( - state.currentStatement, - state.callStack, - state.targets.map { it.location }.toList(), - folder = { if (it.isNotEmpty()) it.min() else UInt.MAX_VALUE } - ) + fun calculateDistanceToTargets(state: State) = + state.targets.minOfOrNull { target -> + distanceCalculator.calculateDistance( + state.currentStatement, + state.callStack, + target.location + ) + } ?: UInt.MAX_VALUE if (random == null) { return WeightedPathSelector( @@ -195,3 +197,50 @@ internal fun , State : UState<*, *, Method, Statement, *, Target, State>> createTargetedPathSelector( + distanceStatistics: DistanceStatistics, + applicationGraph: ApplicationGraph, + callGraphReachabilityStatistics: CallGraphReachabilityStatistics? = null, + random: Random? = null, +): UPathSelector { + val distanceCalculator = DynamicTargetsShortestDistanceCalculator { m, s -> + InterprocDistanceCalculator( + targetLocation = m to s, + applicationGraph = applicationGraph, + getCfgDistance = distanceStatistics::getShortestCfgDistance, + getCfgDistanceToExitPoint = distanceStatistics::getShortestCfgDistanceToExitPoint, + checkReachabilityInCallGraph = if (callGraphReachabilityStatistics != null) (callGraphReachabilityStatistics::checkReachability) else { m1, m2 -> m1 == m2 } + ) + } + + fun calculateWeight(state: State) = + state.targets.minOfOrNull { target -> + distanceCalculator.calculateDistance( + state.currentStatement, + state.callStack, + target.location + ).logWeight() + } ?: UInt.MAX_VALUE + + if (random == null) { + return WeightedPathSelector( + priorityCollectionFactory = { DeterministicPriorityCollection(Comparator.naturalOrder()) }, + weighter = ::calculateWeight + ) + } + + return WeightedPathSelector( + priorityCollectionFactory = { RandomizedPriorityCollection(compareById()) { random.nextDouble() } }, + weighter = { 1.0 / max(calculateWeight(it).toDouble(), 1.0) } + ) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphReachabilityStatistics.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphReachabilityStatistics.kt new file mode 100644 index 0000000000..4e29e4efaa --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphReachabilityStatistics.kt @@ -0,0 +1,20 @@ +package org.usvm.statistics.distances + +import org.usvm.statistics.ApplicationGraph +import org.usvm.util.limitedBfsTraversal +import java.util.concurrent.ConcurrentHashMap + +class CallGraphReachabilityStatistics( + private val depthLimit: UInt, + private val applicationGraph: ApplicationGraph +) { + private val cache = ConcurrentHashMap>() + + private fun getAdjacentVertices(vertex: Method): Sequence = + applicationGraph.statementsOf(vertex).flatMap(applicationGraph::callees).distinct() + + fun checkReachability(methodFrom: Method, methodTo: Method): Boolean = + cache.computeIfAbsent(methodFrom) { + limitedBfsTraversal(depthLimit, listOf(methodFrom), ::getAdjacentVertices).toSet() + }.contains(methodTo) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/DistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt similarity index 69% rename from usvm-core/src/main/kotlin/org/usvm/ps/DistanceCalculator.kt rename to usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt index f1c55dc66d..62f511c58e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/DistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt @@ -1,14 +1,11 @@ -package org.usvm.ps +package org.usvm.statistics.distances -import org.usvm.UCallStackFrame +import org.usvm.UCallStack import kotlin.math.min -fun interface StaticTargetsDistanceCalculator { - fun calculateDistance(currentStatement: Statement, callStack: Collection>): Distance -} - /** - * Calculates shortest distances from location (represented as statement and call stack) to the set of targets. + * Calculates shortest distances from location (represented as statement and call stack) to the set of targets + * considering only CFGs of methods on the call stack. * * Distances in graph remain the same, only the targets can change, so the local CFG distances are * cached while the targets of the method remain the same. @@ -19,7 +16,7 @@ fun interface StaticTargetsDistanceCalculator { * @param getCfgDistanceToExitPoint function with the following signature: * (method, stmt) -> shortest CFG distance from stmt to any of method's exit points. */ -class RoughIterprocShortestDistanceCalculator( +class CallStackDistanceCalculator( targets: Collection>, private val getCfgDistance: (Method, Statement, Statement) -> UInt, private val getCfgDistanceToExitPoint: (Method, Statement) -> UInt @@ -59,7 +56,7 @@ class RoughIterprocShortestDistanceCalculator( return wasRemoved } - override fun calculateDistance(currentStatement: Statement, callStack: Collection>): UInt { + override fun calculateDistance(currentStatement: Statement, callStack: UCallStack): UInt { var currentMinDistanceToTarget = UInt.MAX_VALUE val callStackArray = callStack.toTypedArray() @@ -91,30 +88,3 @@ class RoughIterprocShortestDistanceCalculator( return currentMinDistanceToTarget } } - -class DynamicTargetsShortestDistanceCalculator( - private val getDistanceCalculator: (Method, Statement) -> StaticTargetsDistanceCalculator -) { - private val calculatorsByTarget = HashMap, StaticTargetsDistanceCalculator>() - - fun removeTargetFromCache(target: Pair): Boolean { - return calculatorsByTarget.remove(target) != null - } - - fun calculateDistance( - currentStatement: Statement, - callStack: Collection>, - target: Pair - ): Distance { - val calculator = calculatorsByTarget.computeIfAbsent(target) { getDistanceCalculator(it.first, it.second) } - return calculator.calculateDistance(currentStatement, callStack) - } - - fun calculateDistance( - currentStatement: Statement, - callStack: Collection>, - targets: Collection>, - folder: (Collection) -> Distance - ): Distance = - folder(targets.map { calculateDistance(currentStatement, callStack, it) }) -} diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt new file mode 100644 index 0000000000..43cd58de08 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt @@ -0,0 +1,27 @@ +package org.usvm.statistics.distances + +import org.usvm.UCallStack + +fun interface StaticTargetsDistanceCalculator { + fun calculateDistance(currentStatement: Statement, callStack: UCallStack): Distance +} + +class DynamicTargetsShortestDistanceCalculator( + private val getDistanceCalculator: (Method, Statement) -> StaticTargetsDistanceCalculator +) { + private val calculatorsByTarget = HashMap, StaticTargetsDistanceCalculator>() + + // TODO: use + fun removeTargetFromCache(target: Pair): Boolean { + return calculatorsByTarget.remove(target) != null + } + + fun calculateDistance( + currentStatement: Statement, + callStack: UCallStack, + target: Pair + ): Distance { + val calculator = calculatorsByTarget.computeIfAbsent(target) { getDistanceCalculator(it.first, it.second) } + return calculator.calculateDistance(currentStatement, callStack) + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/DistanceStatistics.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceStatistics.kt similarity index 95% rename from usvm-core/src/main/kotlin/org/usvm/statistics/DistanceStatistics.kt rename to usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceStatistics.kt index 2307187314..c2646d1c10 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/DistanceStatistics.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceStatistics.kt @@ -1,7 +1,8 @@ -package org.usvm.statistics +package org.usvm.statistics.distances import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.toImmutableMap +import org.usvm.statistics.ApplicationGraph import org.usvm.algorithms.findMinDistancesInUnweightedGraph import java.util.concurrent.ConcurrentHashMap diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt new file mode 100644 index 0000000000..3308553c05 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt @@ -0,0 +1,89 @@ +package org.usvm.statistics.distances + +import org.usvm.UCallStack +import org.usvm.statistics.ApplicationGraph + +enum class ReachabilityKind { + LOCAL, + UP_STACK, + DOWN_STACK, + NONE +} + +// TODO: add more information about the path +// TODO: add new targets according to the path? +data class InterprocDistance(val distance: UInt, val reachabilityKind: ReachabilityKind) { + val isUnreachable = reachabilityKind == ReachabilityKind.NONE +} + +// TODO: calculate distance in blocks?? +// TODO: give priority to paths without calls +internal class InterprocDistanceCalculator( + private val targetLocation: Pair, + private val applicationGraph: ApplicationGraph, + private val getCfgDistance: (Method, Statement, Statement) -> UInt, + private val getCfgDistanceToExitPoint: (Method, Statement) -> UInt, + private val checkReachabilityInCallGraph: (Method, Method) -> Boolean +) : StaticTargetsDistanceCalculator { + + private val frameDistanceCache = HashMap>() + + private fun calculateFrameDistance(method: Method, statement: Statement): Pair { + if (method == targetLocation.first) { + val localDistance = getCfgDistance(method, statement, targetLocation.second) + if (localDistance != UInt.MAX_VALUE) { + return localDistance to true + } + } + + val cached = frameDistanceCache[method]?.get(statement) + if (cached != null) { + return cached to false + } + + var minDistanceToCall = UInt.MAX_VALUE + for (statementOfMethod in applicationGraph.statementsOf(method)) { + if (!applicationGraph.callees(statementOfMethod).any()) { + continue + } + + val distanceToCall = getCfgDistance(method, statement, statementOfMethod) + if (distanceToCall >= minDistanceToCall) { + continue + } + + if (applicationGraph.callees(statementOfMethod).any { checkReachabilityInCallGraph(it, targetLocation.first) }) { + minDistanceToCall = distanceToCall + } + } + + if (minDistanceToCall != UInt.MAX_VALUE) { + frameDistanceCache.computeIfAbsent(method) { HashMap() }[statement] = minDistanceToCall + } + return minDistanceToCall to false + } + + override fun calculateDistance( + currentStatement: Statement, + callStack: UCallStack + ): InterprocDistance { + val lastMethod = callStack.lastMethod() + val (lastFrameDistance, isLocal) = calculateFrameDistance(lastMethod, currentStatement) + if (lastFrameDistance != UInt.MAX_VALUE) { + return InterprocDistance(lastFrameDistance, if (isLocal) ReachabilityKind.LOCAL else ReachabilityKind.UP_STACK) + } + + var statementOnCallStack = callStack.last().returnSite + for ((methodOnCallStack, returnSite) in callStack.reversed().drop(1)) { + checkNotNull(statementOnCallStack) { "Not first call stack frame had null return site" } + + if (applicationGraph.successors(statementOnCallStack).any { calculateFrameDistance(methodOnCallStack, it).first != UInt.MAX_VALUE }) { + return InterprocDistance(getCfgDistanceToExitPoint(lastMethod, currentStatement), ReachabilityKind.DOWN_STACK) + } + + statementOnCallStack = returnSite + } + + return InterprocDistance(UInt.MAX_VALUE, ReachabilityKind.NONE) + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/stopstrategies/StepLimitStopStrategy.kt b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/StepLimitStopStrategy.kt index e2d428741b..12bf96e1de 100644 --- a/usvm-core/src/main/kotlin/org/usvm/stopstrategies/StepLimitStopStrategy.kt +++ b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/StepLimitStopStrategy.kt @@ -7,6 +7,6 @@ class StepLimitStopStrategy(private val limit: ULong) : StopStrategy { private var counter = 0UL override fun shouldStop(): Boolean { - return counter++ > limit + return ++counter > limit } } diff --git a/usvm-core/src/test/kotlin/org/usvm/PathTests.kt b/usvm-core/src/test/kotlin/org/usvm/PathTests.kt index 39f0a6d5e3..0e530073f0 100644 --- a/usvm-core/src/test/kotlin/org/usvm/PathTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/PathTests.kt @@ -13,10 +13,10 @@ class PathTests { val root = RootNode() - val firstRealNode = root.pathLocationFor(TestInstruction(1), initialState) + val firstRealNode = root.pathLocationFor(TestInstruction("", 1), initialState) - val updatedInitialState = firstRealNode.pathLocationFor(statement = TestInstruction(2), initialState) - val forkedState = firstRealNode.pathLocationFor(statement = TestInstruction(3), fork) + val updatedInitialState = firstRealNode.pathLocationFor(statement = TestInstruction("",2), initialState) + val forkedState = firstRealNode.pathLocationFor(statement = TestInstruction("", 3), fork) assertTrue { root.children.size == 1 } assertTrue { root.children.values.single() == firstRealNode } @@ -38,10 +38,10 @@ class PathTests { val root = RootNode() - val firstRealNode = root.pathLocationFor(TestInstruction(1), firstState) + val firstRealNode = root.pathLocationFor(TestInstruction("", 1), firstState) - val updatedFirstState = firstRealNode.pathLocationFor(statement = TestInstruction(2), firstState) - val updatedSecondState = firstRealNode.pathLocationFor(statement = TestInstruction(2), secondState) + val updatedFirstState = firstRealNode.pathLocationFor(statement = TestInstruction("", 2), firstState) + val updatedSecondState = firstRealNode.pathLocationFor(statement = TestInstruction("", 2), secondState) assertSame(updatedFirstState, updatedSecondState) diff --git a/usvm-core/src/test/kotlin/org/usvm/TestApplicationGraph.kt b/usvm-core/src/test/kotlin/org/usvm/TestApplicationGraph.kt index 4f91c59a3b..e87ad7f549 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestApplicationGraph.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestApplicationGraph.kt @@ -2,9 +2,7 @@ package org.usvm import org.usvm.statistics.ApplicationGraph -internal data class TestInstruction(val method: String, val offset: Int) { - constructor(offset: Int) : this("", offset) -} +data class TestInstruction(val method: String, val offset: Int) internal interface TestMethodGraphBuilder { fun entryPoint(offset: Int) @@ -19,7 +17,7 @@ internal interface TestApplicationGraphBuilder { } private class TestMethodGraphBuilderImpl(val name: String, instructionsCount: Int) : TestMethodGraphBuilder { - val adjacencyLists = Array(instructionsCount) { mutableSetOf(it) } + val adjacencyLists = Array(instructionsCount) { mutableSetOf() } val calleesByOffset = mutableMapOf() val offsetsByCallee = mutableMapOf>() val entryPoints = mutableSetOf() diff --git a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt index ac45fe9278..0debadbb47 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt @@ -40,7 +40,7 @@ internal class TestState( callStack: UCallStack, pathConstraints: UPathConstraints, memory: UMemoryBase, models: List>, pathLocation: PathsTrieNode, - targetTrees: List = emptyList() + targetTrees: Collection = emptyList() ) : UState(ctx, callStack, pathConstraints, memory, models, pathLocation, targetTrees) { override fun clone(newConstraints: UPathConstraints?): TestState = this @@ -64,10 +64,21 @@ interface TestKeyInfo> : USymbolicCollectionKeyInfo override fun bottomRegion(): Reg = shouldNotBeCalled() } -internal fun mockState(id: StateId, currentLocation: TestInstruction, callStack: UCallStack, targets: List): TestState { +internal fun mockState(id: StateId, startMethod: String, startInstruction: Int = 0, targets: List = emptyList()): TestState { val ctxMock = mockk() every { ctxMock.getNextStateId() } returns id + val callStack = UCallStack(startMethod) val spyk = spyk(TestState(ctxMock, callStack, mockk(), mockk(), emptyList(), mockk(), targets)) - every { spyk.currentStatement } returns currentLocation + every { spyk.currentStatement } returns TestInstruction(startMethod, startInstruction) return spyk } + +internal fun callStackOf(startMethod: String, vararg elements: Pair): UCallStack { + val callStack = UCallStack(startMethod) + var currentMethod = startMethod + for ((method, instr) in elements) { + callStack.push(method, TestInstruction(currentMethod, instr)) + currentMethod = method + } + return callStack +} diff --git a/usvm-core/src/test/kotlin/org/usvm/ps/ClosestToTargetsPathSelectorIntegrationTests.kt b/usvm-core/src/test/kotlin/org/usvm/ps/ClosestToTargetsPathSelectorIntegrationTests.kt deleted file mode 100644 index eed5d4a6a8..0000000000 --- a/usvm-core/src/test/kotlin/org/usvm/ps/ClosestToTargetsPathSelectorIntegrationTests.kt +++ /dev/null @@ -1,73 +0,0 @@ -package org.usvm.ps - -import io.mockk.every -import org.junit.jupiter.api.Test -import org.usvm.* -import org.usvm.TestInstruction -import org.usvm.appGraph -import org.usvm.statistics.DistanceStatistics -import kotlin.test.assertEquals - -internal class ClosestToTargetsPathSelectorIntegrationTests { - - private val appGraph1 = appGraph { - method("A", 8) { - entryPoint(0) - edge(0, 1) - edge(0, 2) - edge(1, 7) - edge(7, 3) - edge(2, 4) - edge(4, 5) - edge(5, 6) - edge(6, 3) - exitPoint(3) - } - } - - @Test - fun `Smoke test, single target and two states, one is closer`() { - val pathSelector = createClosestToTargetsPathSelector(DistanceStatistics(appGraph1)) - val target = TestTarget("A", 3) - - val state1 = mockState(1u, TestInstruction("A", 1), UCallStack("A"), listOf(target)) - val state2 = mockState(2u, TestInstruction("A", 2), UCallStack("A"), listOf(target)) - - pathSelector.add(listOf(state1, state2)) - assertEquals(state1, pathSelector.peek()) - } - - @Test - fun `Multiple targets smoke test`() { - val pathSelector = createClosestToTargetsPathSelector(DistanceStatistics(appGraph1)) - val target = TestTarget("A", 4).apply { - addChild(TestTarget("A", 3)) - } - - val state1 = mockState(1u, TestInstruction("A", 1), UCallStack("A"), listOf(target)) - val state2 = mockState(2u, TestInstruction("A", 2), UCallStack("A"), listOf(target)) - - pathSelector.add(listOf(state1, state2)) - assertEquals(state2, pathSelector.peek()) - } - - @Test - fun `State steps to target and becomes not closest`() { - val pathSelector = createClosestToTargetsPathSelector(DistanceStatistics(appGraph1)) - val target1 = TestTarget("A", 3) - - val target2 = TestTarget("A", 4).apply { - addChild(TestTarget("A", 3)) - } - - val state1 = mockState(1u, TestInstruction("A", 1), UCallStack("A"), listOf(target1)) - val state2 = mockState(2u, TestInstruction("A", 2), UCallStack("A"), listOf(target2)) - - pathSelector.add(listOf(state1, state2)) - assertEquals(state2, pathSelector.peek()) - target2.reach(state2) - every { state2.currentStatement } returns TestInstruction("A", 4) - pathSelector.update(state2) - assertEquals(state1, pathSelector.peek()) - } -} diff --git a/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt b/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt index c2e75b6837..b58bfdbc1c 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt @@ -9,8 +9,6 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import org.usvm.* -import org.usvm.TestState -import org.usvm.pseudoRandom import kotlin.test.assertEquals internal class RandomTreePathSelectorTests { @@ -19,8 +17,8 @@ internal class RandomTreePathSelectorTests { statement: Int, ) { val node = when (prevNode) { - is RootNode -> PathsTrieNodeImpl(prevNode, TestInstruction(statement), staticState) - is PathsTrieNodeImpl -> PathsTrieNodeImpl(prevNode, TestInstruction(statement), staticState) + is RootNode -> PathsTrieNodeImpl(prevNode, TestInstruction("", statement), staticState) + is PathsTrieNodeImpl -> PathsTrieNodeImpl(prevNode, TestInstruction("", statement), staticState) } fun child(init: TreeBuilder.() -> Unit) { @@ -33,7 +31,7 @@ internal class RandomTreePathSelectorTests { val stmt = nextStatement() with(state) { - pathLocation = node.pathLocationFor(TestInstruction(stmt), this) + pathLocation = node.pathLocationFor(TestInstruction("", stmt), this) } } @@ -51,7 +49,7 @@ internal class RandomTreePathSelectorTests { val rootNode = RootNode() val state1 = mockk() - every { state1.pathLocation } returns PathsTrieNodeImpl(rootNode, statement = TestInstruction(1), state1) + every { state1.pathLocation } returns PathsTrieNodeImpl(rootNode, statement = TestInstruction("", 1), state1) val selector = RandomTreePathSelector(rootNode, { 0 }, 0L) @@ -64,7 +62,7 @@ internal class RandomTreePathSelectorTests { val rootNode = RootNode() val state1 = mockk() - every { state1.pathLocation } returns PathsTrieNodeImpl(rootNode, statement = TestInstruction(1), state1) + every { state1.pathLocation } returns PathsTrieNodeImpl(rootNode, statement = TestInstruction("", 1), state1) val selector = RandomTreePathSelector(rootNode, { 0 }, 0L) diff --git a/usvm-core/src/test/kotlin/org/usvm/ps/RoughIterprocShortestDistanceCalculatorTests.kt b/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt similarity index 93% rename from usvm-core/src/test/kotlin/org/usvm/ps/RoughIterprocShortestDistanceCalculatorTests.kt rename to usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt index 2652d2f8e0..9509551012 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ps/RoughIterprocShortestDistanceCalculatorTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt @@ -1,12 +1,12 @@ -package org.usvm.ps +package org.usvm.statistics -import io.mockk.every import org.junit.jupiter.api.Test import org.usvm.UCallStack +import org.usvm.statistics.distances.CallStackDistanceCalculator import kotlin.test.assertEquals @OptIn(ExperimentalUnsignedTypes::class) -internal class RoughIterprocShortestDistanceCalculatorTests { +internal class CallStackDistanceCalculatorTests { private val methodAShortestDistanceMatrix = arrayOf( uintArrayOf(), @@ -54,7 +54,7 @@ internal class RoughIterprocShortestDistanceCalculatorTests { } } - val calculator = RoughIterprocShortestDistanceCalculator( + val calculator = CallStackDistanceCalculator( setOf("A" to 2, "A" to 3, "A" to 4, "A" to 5, "A" to 6), ::getCfgDistance ) { _, _ -> 1u } @@ -91,7 +91,7 @@ internal class RoughIterprocShortestDistanceCalculatorTests { callStack.push("C", 2) val calculator = - RoughIterprocShortestDistanceCalculator(setOf("C" to 4), ::getCfgDistance, ::getCfgDistanceToExitPoint) + CallStackDistanceCalculator(setOf("C" to 4), ::getCfgDistance, ::getCfgDistanceToExitPoint) assertEquals(10u, calculator.calculateDistance(currentStatment, callStack)) calculator.removeTarget("C", 4) diff --git a/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt b/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt new file mode 100644 index 0000000000..3c32b488ce --- /dev/null +++ b/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt @@ -0,0 +1,332 @@ +package org.usvm.statistics + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import org.usvm.* +import org.usvm.statistics.distances.* +import kotlin.test.assertEquals + +class InterprocDistanceCalculatorTests { + + private val appGraph1 = appGraph { + method("A", 8) { + entryPoint(0) + edge(0, 1) + edge(0, 2) + edge(1, 7) + edge(7, 3) + edge(2, 4) + edge(4, 5) + edge(5, 6) + edge(6, 3) + exitPoint(3) + } + + method("B", 22) { + entryPoint(0) + edge(0, 1) + edge(1, 2) + call(2, "H") + edge(2, 3) + edge(3, 4) + call(4, "D") + edge(4, 5) + edge(5, 6) + edge(6, 7) + + edge(7, 8) + edge(7, 9) + call(8, "C") + call(9, "C") + + edge(2, 10) + edge(10, 11) + edge(11, 12) + edge(12, 10) + edge(12, 6) + edge(6, 18) + + edge(2, 13) + edge(13, 14) + edge(14, 15) + call(14, "E") + call(15, "E") + edge(14, 16) + edge(15, 17) + edge(16, 17) + call(17, "B") + edge(17, 6) + edge(16, 18) + edge(18, 19) + call(19, "F") + + edge(2, 20) + edge(20, 21) + call(21, "C") + + exitPoint(8) + exitPoint(9) + exitPoint(19) + exitPoint(21) + } + + method("C", 2) { + entryPoint(0) + edge(0, 1) + exitPoint(1) + } + + method("D", 9) { + entryPoint(0) + + edge(0, 2) + edge(2, 4) + edge(2, 5) + call(5, "F") + edge(5, 6) + edge(6, 4) + edge(6, 8) + exitPoint(8) + + edge(0, 1) + exitPoint(1) + + edge(2, 3) + exitPoint(3) + + edge(6, 7) + call(7, "C") + exitPoint(7) + } + + method("E", 4) { + entryPoint(0) + edge(0, 1) + edge(1, 2) + call(1, "F") + exitPoint(2) + edge(1, 3) + exitPoint(3) + } + + method("F", 6) { + entryPoint(0) + edge(0, 1) + call(1, "G") + edge(1, 2) + call(2, "G") + edge(2, 3) + call(3, "G") + edge(3, 4) + call(4, "G") + edge(4, 5) + exitPoint(5) + } + + method("G", 8) { + entryPoint(0) + edge(0, 1) + edge(1, 2) + edge(1, 3) + edge(1, 4) + edge(2, 5) + edge(3, 5) + edge(4, 5) + edge(4, 7) + edge(5, 6) + exitPoint(6) + exitPoint(7) + } + + method("H", 2) { + entryPoint(0) + edge(0, 1) + exitPoint(1) + call(1, "I") + } + + method("I", 1) { + entryPoint(0) + exitPoint(0) + call(0, "E") + } + } + + @ParameterizedTest + @MethodSource("testCases") + fun `Interprocedural distance calculator test`( + callGraphReachabilityDepth: Int, + callStack: UCallStack, + fromLoc: TestInstruction, + targetLoc: TestInstruction, + expectedDist: InterprocDistance + ) { + val distanceStatistics = DistanceStatistics(appGraph1) + val callGraphReachabilityStatistics = CallGraphReachabilityStatistics(callGraphReachabilityDepth.toUInt(), appGraph1) + val calculator = InterprocDistanceCalculator( + targetLoc.method to targetLoc, + appGraph1, + distanceStatistics::getShortestCfgDistance, + distanceStatistics::getShortestCfgDistanceToExitPoint, + checkReachabilityInCallGraph = + if (callGraphReachabilityDepth == 0) { m1, m2 -> m1 == m2 } else { m1, m2 -> callGraphReachabilityStatistics.checkReachability(m1, m2) } + ) + assertEquals(expectedDist, calculator.calculateDistance(fromLoc, callStack)) + } + + companion object { + @JvmStatic + fun testCases(): Collection { + return listOf( + Arguments.of( + 0, + callStackOf("B"), + TestInstruction("B", 2), + TestInstruction("B", 18), + InterprocDistance(4u, ReachabilityKind.LOCAL) + ), + Arguments.of( + 0, + callStackOf("B"), + TestInstruction("B", 2), + TestInstruction("D", 1), + InterprocDistance(2u, ReachabilityKind.UP_STACK) + ), + Arguments.of( + 0, + callStackOf("B", "D" to 4, "F" to 5, "G" to 3), + TestInstruction("G", 4), + TestInstruction("B", 18), + InterprocDistance(1u, ReachabilityKind.DOWN_STACK) + ), + Arguments.of( + 0, + callStackOf("B", "D" to 4, "F" to 5, "G" to 3), + TestInstruction("G", 4), + TestInstruction("B", 11), + InterprocDistance(UInt.MAX_VALUE, ReachabilityKind.NONE) + ), + Arguments.of( + 0, + callStackOf("B"), + TestInstruction("B", 2), + TestInstruction("E", 1), + InterprocDistance(2u, ReachabilityKind.UP_STACK) + ), + Arguments.of( + 0, + callStackOf("D", "F" to 5, "G" to 3), + TestInstruction("G", 0), + TestInstruction("D", 4), + InterprocDistance(3u, ReachabilityKind.DOWN_STACK) + ), + Arguments.of( + 0, + callStackOf("B", "F" to 19, "G" to 2), + TestInstruction("G", 5), + TestInstruction("G", 1), + InterprocDistance(1u, ReachabilityKind.DOWN_STACK) + ), + Arguments.of( + 0, + callStackOf("B"), + TestInstruction("B", 13), + TestInstruction("B", 1), + InterprocDistance(3u, ReachabilityKind.UP_STACK) + ), + Arguments.of( + 0, + callStackOf("B"), + TestInstruction("B", 17), + TestInstruction("B", 17), + InterprocDistance(0u, ReachabilityKind.LOCAL) + ), + Arguments.of( + 0, + callStackOf("B"), + TestInstruction("B", 0), + TestInstruction("C", 1), + InterprocDistance(4u, ReachabilityKind.UP_STACK) + ), + // This target can be achieved only with call graph BFS depth > 0 + Arguments.of( + 0, + callStackOf("B"), + TestInstruction("B", 0), + TestInstruction("G", 1), + InterprocDistance(UInt.MAX_VALUE, ReachabilityKind.NONE) + ), + // Going through B-19 + Arguments.of( + 1, + callStackOf("B"), + TestInstruction("B", 0), + TestInstruction("G", 1), + InterprocDistance(7u, ReachabilityKind.UP_STACK) + ), + // Going through B-4 + Arguments.of( + 2, + callStackOf("B"), + TestInstruction("B", 0), + TestInstruction("G", 1), + InterprocDistance(4u, ReachabilityKind.UP_STACK) + ), + // Going through B-4 + Arguments.of( + 3, + callStackOf("B"), + TestInstruction("B", 0), + TestInstruction("G", 1), + InterprocDistance(4u, ReachabilityKind.UP_STACK) + ), + // Going through B-2 + Arguments.of( + 4, + callStackOf("B"), + TestInstruction("B", 0), + TestInstruction("G", 1), + InterprocDistance(2u, ReachabilityKind.UP_STACK) + ), + Arguments.of( + 0, + callStackOf("B", "E" to 14, "F" to 1, "G" to 2), + TestInstruction("G", 3), + TestInstruction("I", 0), + InterprocDistance(UInt.MAX_VALUE, ReachabilityKind.NONE) + ), + Arguments.of( + 1, + callStackOf("B", "E" to 14, "F" to 1, "G" to 2), + TestInstruction("G", 3), + TestInstruction("I", 0), + InterprocDistance(UInt.MAX_VALUE, ReachabilityKind.NONE) + ), + // Going recursively to B through B-17, then through B-2 + Arguments.of( + 2, + callStackOf("B", "E" to 14, "F" to 1, "G" to 2), + TestInstruction("G", 3), + TestInstruction("I", 0), + InterprocDistance(2u, ReachabilityKind.DOWN_STACK) + ), + Arguments.of( + 0, + callStackOf("B", "E" to 15, "F" to 1, "G" to 4), + TestInstruction("G", 3), + TestInstruction("E", 1), + InterprocDistance(UInt.MAX_VALUE, ReachabilityKind.NONE) + ), + // Going recursively to B through B-17, then through B-14 + Arguments.of( + 1, + callStackOf("B", "E" to 15, "F" to 1, "G" to 4), + TestInstruction("G", 3), + TestInstruction("E", 1), + InterprocDistance(2u, ReachabilityKind.DOWN_STACK) + ), + ) + } + } +} diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt index d96a7c0f18..26be8cbd14 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt @@ -5,7 +5,10 @@ import org.jacodb.api.JcClasspath import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst import org.jacodb.api.ext.methods -import org.usvm.* +import org.usvm.CoverageZone +import org.usvm.TargetReproductionOptions +import org.usvm.UMachine +import org.usvm.UMachineOptions import org.usvm.api.targets.JcTarget import org.usvm.machine.interpreter.JcInterpreter import org.usvm.machine.state.JcMethodResult @@ -14,6 +17,7 @@ import org.usvm.machine.state.lastStmt import org.usvm.ps.createPathSelector import org.usvm.ps.createTargetReproductionPathSelector import org.usvm.statistics.* +import org.usvm.statistics.distances.DistanceStatistics import org.usvm.stopstrategies.createStopStrategy val logger = object : KLogging() {}.logger diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt index 5c674f06e5..35d0f2a216 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt @@ -6,12 +6,11 @@ import org.jacodb.api.cfg.JcInst import org.usvm.PathsTrieNode import org.usvm.UCallStack import org.usvm.UState +import org.usvm.api.targets.JcTarget import org.usvm.constraints.UPathConstraints import org.usvm.machine.JcContext import org.usvm.memory.UMemory import org.usvm.model.UModelBase -import org.usvm.PathsTrieNode -import org.usvm.api.targets.JcTarget class JcState( ctx: JcContext, @@ -20,6 +19,7 @@ class JcState( memory: UMemory = UMemory(ctx, pathConstraints.typeConstraints), models: List> = listOf(), override var pathLocation: PathsTrieNode = ctx.mkInitialLocation(), + // TODO: should set be public? var methodResult: JcMethodResult = JcMethodResult.NoCall, targets: List = emptyList() ) : UState( diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt index 4ef7c9e4da..c139774ca7 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt @@ -9,12 +9,8 @@ import org.usvm.language.Program import org.usvm.language.SampleType import org.usvm.language.Stmt import org.usvm.ps.createPathSelector -import org.usvm.statistics.CompositeUMachineObserver -import org.usvm.statistics.CoverageStatistics -import org.usvm.statistics.CoveredNewStatesCollector -import org.usvm.statistics.DistanceStatistics -import org.usvm.statistics.TerminatedStateRemover -import org.usvm.statistics.UMachineObserver +import org.usvm.statistics.* +import org.usvm.statistics.distances.DistanceStatistics import org.usvm.stopstrategies.createStopStrategy /** diff --git a/usvm-util/src/main/kotlin/org/usvm/algorithms/GraphUtils.kt b/usvm-util/src/main/kotlin/org/usvm/algorithms/GraphUtils.kt index 96130b927f..e147b8e5d1 100644 --- a/usvm-util/src/main/kotlin/org/usvm/algorithms/GraphUtils.kt +++ b/usvm-util/src/main/kotlin/org/usvm/algorithms/GraphUtils.kt @@ -90,3 +90,29 @@ inline fun bfsTraversal(startVertices: Collection, crossinline adjacentVe } } } + +fun limitedBfsTraversal(depthLimit: UInt, startVertices: Collection, adjacentVertices: (V) -> Sequence): Sequence { + var currentDepth = 0u + var numberOfVerticesOfCurrentLevel = startVertices.size + var numberOfVerticesOfNextLevel = 0 + + val queue: Queue = LinkedList(startVertices) + val visited = HashSet() + + return sequence { + while (currentDepth <= depthLimit && queue.isNotEmpty()) { + val currentVertex = queue.remove() + visited.add(currentVertex) + yield(currentVertex) + adjacentVertices(currentVertex).filterNot(visited::contains).forEach { + numberOfVerticesOfNextLevel++ + queue.add(it) + } + if (--numberOfVerticesOfCurrentLevel == 0) { + currentDepth++ + numberOfVerticesOfCurrentLevel = numberOfVerticesOfNextLevel + numberOfVerticesOfNextLevel = 0 + } + } + } +} diff --git a/usvm-util/src/main/kotlin/org/usvm/util/MathUtils.kt b/usvm-util/src/main/kotlin/org/usvm/util/MathUtils.kt new file mode 100644 index 0000000000..700e63d17b --- /dev/null +++ b/usvm-util/src/main/kotlin/org/usvm/util/MathUtils.kt @@ -0,0 +1,19 @@ +package org.usvm.util + +fun log2(n: UInt): UInt { + if (n == UInt.MAX_VALUE) { + return 32u + } + + if (n == 0u) { + return 0u + } + + var ret = 0u + var m = n + while (m > 1u) { + m = m shr 1 + ret++ + } + return ret +} diff --git a/usvm-util/src/test/kotlin/org/usvm/algorithms/GraphUtilsTests.kt b/usvm-util/src/test/kotlin/org/usvm/algorithms/GraphUtilsTests.kt deleted file mode 100644 index 61f7bc9e11..0000000000 --- a/usvm-util/src/test/kotlin/org/usvm/algorithms/GraphUtilsTests.kt +++ /dev/null @@ -1,120 +0,0 @@ -package org.usvm.algorithms - -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.MethodSource -import kotlin.test.assertEquals - -internal class SimpleGraph(val vertexCount: Int) { - private val adjacencyLists = Array(vertexCount) { mutableSetOf(it) } - - fun addEdge(fromVertex: Int, toVertex: Int) { - adjacencyLists[fromVertex].add(toVertex) - adjacencyLists[toVertex].add(fromVertex) - } - - fun getAdjacentVertices(vertexIndex: Int): Sequence = adjacencyLists[vertexIndex].asSequence() -} - -internal class GraphUtilsTests { - - @ParameterizedTest - @MethodSource("testCases") - fun findShortestDistancesInUnweightedGraphTest(graph: SimpleGraph, startVertex: Int, expected: Map) { - val foundDistances = findMinDistancesInUnweightedGraph(startVertex, graph::getAdjacentVertices) - assertEquals(expected.size, foundDistances.size) - expected.forEach { (i, d) -> assertEquals(d, foundDistances[i]) } - } - - @ParameterizedTest - @MethodSource("testCases") - fun findShortestDistancesInUnweightedGraphWithCacheTest(graph: SimpleGraph, startVertex: Int, expected: Map) { - val cache = mutableMapOf>() - for (i in 0 until graph.vertexCount) { - if (i == startVertex) { continue } - val foundDistances = findMinDistancesInUnweightedGraph(i, graph::getAdjacentVertices, cache) - val foundWithoutCacheDistances = findMinDistancesInUnweightedGraph(i, graph::getAdjacentVertices) - foundWithoutCacheDistances.forEach { (i, d) -> assertEquals(d, foundDistances[i]) } - cache[i] = foundDistances - } - - val foundDistances = findMinDistancesInUnweightedGraph(startVertex, graph::getAdjacentVertices) - val foundWithoutCacheDistances = findMinDistancesInUnweightedGraph(startVertex, graph::getAdjacentVertices) - - assertEquals(expected.size, foundDistances.size) - foundWithoutCacheDistances.forEach { (i, d) -> assertEquals(d, foundDistances[i]) } - expected.forEach { (i, d) -> assertEquals(d, foundDistances[i]) } - } - - companion object { - @JvmStatic - fun testCases(): Collection { - val graph1 = SimpleGraph(9).apply { - addEdge(0, 1) - addEdge(0, 7) - addEdge(1, 7) - addEdge(1, 2) - addEdge(7, 6) - addEdge(7, 8) - addEdge(2, 8) - addEdge(8, 6) - addEdge(2, 5) - addEdge(2, 3) - addEdge(6, 5) - addEdge(3, 5) - addEdge(3, 4) - addEdge(5, 4) - } - val graph1WithStandaloneVertices = SimpleGraph(30).apply { - addEdge(0, 1) - addEdge(0, 7) - addEdge(1, 7) - addEdge(1, 2) - addEdge(7, 6) - addEdge(7, 8) - addEdge(2, 8) - addEdge(8, 6) - addEdge(2, 5) - addEdge(2, 3) - addEdge(6, 5) - addEdge(3, 5) - addEdge(3, 4) - addEdge(5, 4) - } - val graph1Expected = mapOf( - 0 to 0u, - 1 to 1u, - 2 to 2u, - 3 to 3u, - 4 to 4u, - 5 to 3u, - 6 to 2u, - 7 to 1u, - 8 to 2u - ) - val graph2 = SimpleGraph(6).apply { - addEdge(0, 1) - addEdge(0, 2) - addEdge(0, 3) - addEdge(2, 4) - addEdge(3, 5) - addEdge(4, 5) - } - val graph2Expected = mapOf( - 0 to 0u, - 1 to 1u, - 2 to 1u, - 3 to 1u, - 4 to 2u, - 5 to 2u - ) - - return listOf( - Arguments.of(graph1, 0, graph1Expected), - Arguments.of(graph1WithStandaloneVertices, 0, graph1Expected), - Arguments.of(graph1WithStandaloneVertices, 15, mapOf(15 to 0u)), - Arguments.of(graph2, 0, graph2Expected), - ) - } - } -} From a5b9c066a71ec444ef457703dd0d8caf9d004331 Mon Sep 17 00:00:00 2001 From: Maksim Parshin Date: Wed, 30 Aug 2023 18:41:42 +0300 Subject: [PATCH 03/17] Connect targets infrastructure with API --- usvm-core/src/main/kotlin/org/usvm/Targets.kt | 12 ++- .../kotlin/org/usvm/ps/PathSelectorFactory.kt | 77 ++++++++++++------- .../CoveredNewStatesCollector.kt | 9 ++- .../statistics/collectors/StatesCollector.kt | 7 ++ .../TargetsReachedStatesCollector.kt | 6 +- .../stopstrategies/StopStrategyFactory.kt | 6 ++ .../TargetsReachedStopStrategy.kt | 7 ++ .../src/test/kotlin/org/usvm/TestUtil.kt | 2 +- .../org/usvm/api/targets/JcExitTarget.kt | 10 +++ .../org/usvm/api/targets/JcLocationTarget.kt | 4 +- .../targets/JcNullPointerDereferenceTarget.kt | 4 +- .../kotlin/org/usvm/api/targets/JcTarget.kt | 3 +- .../main/kotlin/org/usvm/machine/JcMachine.kt | 74 +++++++----------- .../org/usvm/samples/JavaMethodTestRunner.kt | 28 +++++-- .../targets/TestNullPointerDereference.kt | 45 ++++++----- .../src/test/kotlin/org/usvm/util/Util.kt | 12 +++ .../kotlin/org/usvm/machine/SampleMachine.kt | 37 ++++++--- .../kotlin/org/usvm/machine/SampleTarget.kt | 2 +- .../main/kotlin/org/usvm/UMachineOptions.kt | 36 ++++----- 19 files changed, 230 insertions(+), 151 deletions(-) rename usvm-core/src/main/kotlin/org/usvm/statistics/{ => collectors}/CoveredNewStatesCollector.kt (82%) create mode 100644 usvm-core/src/main/kotlin/org/usvm/statistics/collectors/StatesCollector.kt rename usvm-core/src/main/kotlin/org/usvm/statistics/{ => collectors}/TargetsReachedStatesCollector.kt (67%) create mode 100644 usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt create mode 100644 usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcExitTarget.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/Targets.kt b/usvm-core/src/main/kotlin/org/usvm/Targets.kt index aab409bb5d..d0e349d84f 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Targets.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Targets.kt @@ -3,14 +3,11 @@ package org.usvm import org.usvm.statistics.UMachineObserver abstract class UTarget, State : UState<*, *, Method, Statement, *, Target, State>>( - val method: Method, - val statement: Statement + val location: Pair? = null ) : UMachineObserver { private val childrenImpl = mutableListOf() private var parent: Target? = null - val location = method to statement - val children: List = childrenImpl val isSink get() = childrenImpl.isEmpty() @@ -60,3 +57,10 @@ abstract class UTarget, State : UState<*, *, Method, Statement, *, Target, State>> : UTarget(null) { + override fun onStateTerminated(state: State) { + visit(state) + super.onStateTerminated(state) + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt index f25f7322ee..f2ba517636 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt @@ -8,21 +8,28 @@ import org.usvm.util.log2 import kotlin.math.max import kotlin.random.Random -fun > createPathSelector( +fun , State : UState<*, Method, Statement, *, Target, State>> createPathSelector( initialState: State, options: UMachineOptions, + applicationGraph: ApplicationGraph, coverageStatistics: () -> CoverageStatistics? = { null }, - distanceStatistics: () -> DistanceStatistics? = { null }, + distanceStatistics: () -> DistanceStatistics? = { null } ): UPathSelector { val strategies = options.pathSelectionStrategies require(strategies.isNotEmpty()) { "At least one path selector strategy should be specified" } val random by lazy { Random(options.randomSeed) } + val callGraphReachabilityStatistics = + if (options.targetSearchDepth > 0u) { + CallGraphReachabilityStatistics(options.targetSearchDepth, applicationGraph) + } else null + val selectors = strategies.map { strategy -> when (strategy) { PathSelectionStrategy.BFS -> BfsPathSelector() PathSelectionStrategy.DFS -> DfsPathSelector() + PathSelectionStrategy.RANDOM_PATH -> RandomTreePathSelector( // Initial state is the first `real` node, not the root. root = requireNotNull(initialState.pathLocation.parent), @@ -31,18 +38,39 @@ fun > creat PathSelectionStrategy.DEPTH -> createDepthPathSelector() PathSelectionStrategy.DEPTH_RANDOM -> createDepthPathSelector(random) + PathSelectionStrategy.FORK_DEPTH -> createForkDepthPathSelector() PathSelectionStrategy.FORK_DEPTH_RANDOM -> createForkDepthPathSelector(random) + PathSelectionStrategy.CLOSEST_TO_UNCOVERED -> createClosestToUncoveredPathSelector( requireNotNull(coverageStatistics()) { "Coverage statistics is required for closest to uncovered path selector" }, requireNotNull(distanceStatistics()) { "Distance statistics is required for closest to uncovered path selector" } ) - PathSelectionStrategy.CLOSEST_TO_UNCOVERED_RANDOM -> createClosestToUncoveredPathSelector( requireNotNull(coverageStatistics()) { "Coverage statistics is required for closest to uncovered path selector" }, requireNotNull(distanceStatistics()) { "Distance statistics is required for closest to uncovered path selector" }, random ) + + PathSelectionStrategy.TARGETED -> createTargetedPathSelector( + requireNotNull(distanceStatistics()) { "Distance statistics is required for targeted path selector" }, + applicationGraph, + callGraphReachabilityStatistics + ) + PathSelectionStrategy.TARGETED_RANDOM -> createTargetedPathSelector( + requireNotNull(distanceStatistics()) { "Distance statistics is required for targeted path selector" }, + applicationGraph, + callGraphReachabilityStatistics, + random + ) + + PathSelectionStrategy.TARGETED_CALL_STACK_LOCAL -> createTargetedPathSelector( + requireNotNull(distanceStatistics()) { "Distance statistics is required for targeted call stack local path selector" } + ) + PathSelectionStrategy.TARGETED_CALL_STACK_LOCAL_RANDOM -> createTargetedPathSelector( + requireNotNull(distanceStatistics()) { "Distance statistics is required for targeted call stack local path selector" }, + random + ) } } @@ -80,21 +108,6 @@ fun > creat return selector } -fun , State : UState<*, *, Method, Statement, *, Target, State>> createTargetReproductionPathSelector( - initialState: State, - options: TargetReproductionOptions, - distanceStatistics: DistanceStatistics -): UPathSelector { - val random = - when(options.pathSelectionStrategy) { - TargetReproductionPathSelectionStrategy.RANDOMIZED -> Random(options.randomSeed) - TargetReproductionPathSelectionStrategy.DETERMINISTIC -> null - } - val selector = createTargetedPathSelector(distanceStatistics, random) - selector.add(listOf(initialState)) - return selector -} - /** * Wraps the selector into an [ExceptionPropagationPathSelector] if [propagateExceptions] is true. */ @@ -178,11 +191,15 @@ internal fun - distanceCalculator.calculateDistance( - state.currentStatement, - state.callStack, - target.location - ) + if (target.location == null) { + 0u // i. e. ExitTarget case + } else { + distanceCalculator.calculateDistance( + state.currentStatement, + state.callStack, + target.location + ) + } } ?: UInt.MAX_VALUE if (random == null) { @@ -225,11 +242,15 @@ internal fun - distanceCalculator.calculateDistance( - state.currentStatement, - state.callStack, - target.location - ).logWeight() + if (target.location == null) { + 0u // i. e. ExitTarget case + } else { + distanceCalculator.calculateDistance( + state.currentStatement, + state.callStack, + target.location + ).logWeight() + } } ?: UInt.MAX_VALUE if (random == null) { diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/CoveredNewStatesCollector.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/CoveredNewStatesCollector.kt similarity index 82% rename from usvm-core/src/main/kotlin/org/usvm/statistics/CoveredNewStatesCollector.kt rename to usvm-core/src/main/kotlin/org/usvm/statistics/collectors/CoveredNewStatesCollector.kt index af3d378822..53cc88789c 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/CoveredNewStatesCollector.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/CoveredNewStatesCollector.kt @@ -1,4 +1,7 @@ -package org.usvm.statistics +package org.usvm.statistics.collectors + +import org.usvm.statistics.CoverageStatistics +import org.usvm.statistics.UMachineObserver /** * [UMachineObserver] which collects states if the coverage increased or if the @@ -10,9 +13,9 @@ package org.usvm.statistics class CoveredNewStatesCollector( private val coverageStatistics: CoverageStatistics<*, *, *>, private val isException: (State) -> Boolean -) : UMachineObserver { +) : StatesCollector { private val mutableCollectedStates = mutableListOf() - val collectedStates: List = mutableCollectedStates + override val collectedStates: List = mutableCollectedStates private var previousCoveredStatements = coverageStatistics.getTotalCoveredStatements() diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/StatesCollector.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/StatesCollector.kt new file mode 100644 index 0000000000..1e262e7404 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/StatesCollector.kt @@ -0,0 +1,7 @@ +package org.usvm.statistics.collectors + +import org.usvm.statistics.UMachineObserver + +interface StatesCollector : UMachineObserver { + val collectedStates: List +} diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/TargetsReachedStatesCollector.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt similarity index 67% rename from usvm-core/src/main/kotlin/org/usvm/statistics/TargetsReachedStatesCollector.kt rename to usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt index e9e7413b62..2ebbe9aaad 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/TargetsReachedStatesCollector.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt @@ -1,10 +1,10 @@ -package org.usvm.statistics +package org.usvm.statistics.collectors import org.usvm.UState -class TargetsReachedStatesCollector> : UMachineObserver { +class TargetsReachedStatesCollector> : StatesCollector { private val mutableCollectedStates = mutableListOf() - val collectedStates: List = mutableCollectedStates + override val collectedStates: List = mutableCollectedStates override fun onStateTerminated(state: State) { if (state.reachedSinks.isNotEmpty()) { diff --git a/usvm-core/src/main/kotlin/org/usvm/stopstrategies/StopStrategyFactory.kt b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/StopStrategyFactory.kt index f1e78d344c..27a67c2d5e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/stopstrategies/StopStrategyFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/StopStrategyFactory.kt @@ -1,10 +1,12 @@ package org.usvm.stopstrategies import org.usvm.UMachineOptions +import org.usvm.UTarget import org.usvm.statistics.CoverageStatistics fun createStopStrategy( options: UMachineOptions, + targets: Collection>, coverageStatistics: () -> CoverageStatistics<*, *, *>? = { null }, getCollectedStatesCount: (() -> Int)? = null, ) : StopStrategy { @@ -43,6 +45,10 @@ fun createStopStrategy( stopStrategies.add(stepsFromLastCoveredStopStrategy) } + if (options.stopOnTargetsReached) { + stopStrategies.add(TargetsReachedStopStrategy(targets)) + } + if (stopStrategies.isEmpty()) { return StopStrategy { false } } diff --git a/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt new file mode 100644 index 0000000000..4871ae4a98 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt @@ -0,0 +1,7 @@ +package org.usvm.stopstrategies + +import org.usvm.UTarget + +class TargetsReachedStopStrategy(private val targets: Collection>) : StopStrategy { + override fun shouldStop(): Boolean = targets.all { it.isRemoved } +} diff --git a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt index 0debadbb47..3a965be34f 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt @@ -29,7 +29,7 @@ internal fun pseudoRandom(i: Int): Int { return res } -internal class TestTarget(method: String, offset: Int) : UTarget(method, TestInstruction(method, offset)) { +internal class TestTarget(method: String, offset: Int) : UTarget(method to TestInstruction(method, offset)) { fun reach(state: TestState) { visit(state) } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcExitTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcExitTarget.kt new file mode 100644 index 0000000000..e86d8556c6 --- /dev/null +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcExitTarget.kt @@ -0,0 +1,10 @@ +package org.usvm.api.targets + +import org.usvm.machine.state.JcState + +class JcExitTarget : JcTarget() { + override fun onStateTerminated(state: JcState) { + visit(state) + super.onStateTerminated(state) + } +} diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt index 7ef6c54502..1f376ee4d6 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt @@ -4,10 +4,10 @@ import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst import org.usvm.machine.state.JcState -class JcLocationTarget(method: JcMethod, inst: JcInst) : JcTarget(method, inst) { +class JcLocationTarget(private val method: JcMethod, private val inst: JcInst) : JcTarget(method to inst) { private fun hasReached(state: JcState): Boolean { - return state.callStack.isNotEmpty() && method == state.callStack.lastMethod() && statement == state.currentStatement + return state.callStack.isNotEmpty() && method == state.callStack.lastMethod() && inst == state.currentStatement } override fun onState(parent: JcState, forks: Sequence) { diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt index 5633f640e3..9eb38cd435 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt @@ -5,10 +5,10 @@ import org.jacodb.api.cfg.JcInst import org.usvm.UHeapRef import org.usvm.machine.state.JcState -class JcNullPointerDereferenceTarget(method: JcMethod, inst: JcInst) : JcTarget(method, inst) { +class JcNullPointerDereferenceTarget(private val method: JcMethod, private val inst: JcInst) : JcTarget(method to inst) { override fun onNullPointerDereference(state: JcState, ref: UHeapRef) { - if (state.callStack.lastMethod() == method && state.currentStatement == statement) { + if (state.callStack.lastMethod() == method && state.currentStatement == inst) { visit(state) } super.onNullPointerDereference(state, ref) diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt index 4c9e1d3599..f53401bafc 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt @@ -7,7 +7,8 @@ import org.usvm.UTarget import org.usvm.machine.interpreter.JcInterpreterObserver import org.usvm.machine.state.JcState -open class JcTarget(method: JcMethod, inst: JcInst) : UTarget(method, inst), JcInterpreterObserver { +open class JcTarget(location: Pair? = null +) : UTarget(location), JcInterpreterObserver { override fun onNullPointerDereference(state: JcState, ref: UHeapRef) { forEachChild { onNullPointerDereference(state, ref) } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt index 26be8cbd14..bfdf1e6b08 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt @@ -6,7 +6,7 @@ import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst import org.jacodb.api.ext.methods import org.usvm.CoverageZone -import org.usvm.TargetReproductionOptions +import org.usvm.StateCollectionStrategy import org.usvm.UMachine import org.usvm.UMachineOptions import org.usvm.api.targets.JcTarget @@ -15,8 +15,9 @@ import org.usvm.machine.state.JcMethodResult import org.usvm.machine.state.JcState import org.usvm.machine.state.lastStmt import org.usvm.ps.createPathSelector -import org.usvm.ps.createTargetReproductionPathSelector import org.usvm.statistics.* +import org.usvm.statistics.collectors.CoveredNewStatesCollector +import org.usvm.statistics.collectors.TargetsReachedStatesCollector import org.usvm.statistics.distances.DistanceStatistics import org.usvm.stopstrategies.createStopStrategy @@ -36,9 +37,9 @@ class JcMachine( private val distanceStatistics = DistanceStatistics(applicationGraph) - fun analyze(method: JcMethod): List { - logger.debug("$this.analyze($method)") - val initialState = interpreter.getInitialState(method) + fun analyze(method: JcMethod, targets: List = emptyList()): List { + logger.debug("$this.analyze($method, $targets)") + val initialState = interpreter.getInitialState(method, targets) val methodsToTrackCoverage = when (options.coverageZone) { @@ -58,22 +59,29 @@ class JcMachine( val pathSelector = createPathSelector( initialState, options, + applicationGraph, { coverageStatistics }, { distanceStatistics } ) - val statesCollector = CoveredNewStatesCollector(coverageStatistics) { - it.methodResult is JcMethodResult.JcException - } + val statesCollector = + when (options.stateCollectionStrategy) { + StateCollectionStrategy.COVERED_NEW -> CoveredNewStatesCollector(coverageStatistics) { + it.methodResult is JcMethodResult.JcException + } + StateCollectionStrategy.REACHED_TARGET -> TargetsReachedStatesCollector() + } + val stopStrategy = createStopStrategy( options, + targets, coverageStatistics = { coverageStatistics }, getCollectedStatesCount = { statesCollector.collectedStates.size } ) val observers = mutableListOf>(coverageStatistics) - observers.add(TerminatedStateRemover()) + observers.addAll(targets) if (options.coverageZone == CoverageZone.TRANSITIVE) { observers.add( @@ -87,48 +95,24 @@ class JcMachine( } observers.add(statesCollector) - run( - interpreter, - pathSelector, - observer = CompositeUMachineObserver(observers), - isStateTerminated = ::isStateTerminated, - stopStrategy = stopStrategy, - ) - - return statesCollector.collectedStates - } - - fun reproduceTargets( - method: JcMethod, - targets: List, - options: TargetReproductionOptions = TargetReproductionOptions() - ): List { - logger.debug("$this.reproduceTargets($method)") - val initialState = interpreter.getInitialState(method, targets) - val pathSelector = createTargetReproductionPathSelector(initialState, options, distanceStatistics) - - // TODO: gracefully remove targets.forEach { ctx.jcInterpreterObservers += it } + try { + run( + interpreter, + pathSelector, + observer = CompositeUMachineObserver(observers), + isStateTerminated = ::isStateTerminated, + stopStrategy = stopStrategy, + ) + } finally { + targets.forEach { ctx.jcInterpreterObservers -= it } + } - val statesCollector = TargetsReachedStatesCollector() - val observers = mutableListOf( - statesCollector, - TerminatedStateRemover() - ) - observers.addAll(targets) - - run( - interpreter, - pathSelector, - observer = CompositeUMachineObserver(observers), - isStateTerminated = ::isStateTerminated, - // TODO: configure - stopStrategy = { false }, - ) return statesCollector.collectedStates } + private fun isStateTerminated(state: JcState): Boolean { return state.callStack.isEmpty() } diff --git a/usvm-jvm/src/test/kotlin/org/usvm/samples/JavaMethodTestRunner.kt b/usvm-jvm/src/test/kotlin/org/usvm/samples/JavaMethodTestRunner.kt index 3e35f3f268..fb32eb5bc3 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/samples/JavaMethodTestRunner.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/samples/JavaMethodTestRunner.kt @@ -9,17 +9,13 @@ import org.usvm.UMachineOptions import org.usvm.api.JcClassCoverage import org.usvm.api.JcParametersState import org.usvm.api.JcTest +import org.usvm.api.targets.JcTarget import org.usvm.api.util.JcTestResolver import org.usvm.machine.JcMachine import org.usvm.test.util.TestRunner import org.usvm.test.util.checkers.AnalysisResultsNumberMatcher import org.usvm.test.util.checkers.ignoreNumberOfAnalysisResults -import kotlin.reflect.KClass -import kotlin.reflect.KFunction -import kotlin.reflect.KFunction1 -import kotlin.reflect.KFunction2 -import kotlin.reflect.KFunction3 -import kotlin.reflect.KFunction4 +import kotlin.reflect.* import kotlin.reflect.full.instanceParameter import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaMethod @@ -27,6 +23,22 @@ import kotlin.reflect.jvm.javaMethod @TestInstance(TestInstance.Lifecycle.PER_CLASS) open class JavaMethodTestRunner : TestRunner, KClass<*>?, JcClassCoverage>() { + + private var targets: List = emptyList() + + /** + * Sets JcTargets to run JcMachine with in the scope of [action]. + */ + protected fun withTargets(targets: List, action: () -> T): T { + val prevTargets = this.targets + try { + this.targets = targets + return action() + } finally { + this.targets = prevTargets + } + } + // region Default checkers protected inline fun checkExecutionBranches( @@ -709,7 +721,7 @@ open class JavaMethodTestRunner : TestRunner, KClass<*>?, J return values } - private val cp = JacoDBContainer(samplesKey).cp + protected val cp = JacoDBContainer(samplesKey).cp private val testResolver = JcTestResolver() @@ -732,7 +744,7 @@ open class JavaMethodTestRunner : TestRunner, KClass<*>?, J val jcMethod = jcClass.declaredMethods.first { it.name == method.name } JcMachine(cp, options).use { machine -> - val states = machine.analyze(jcMethod.method) + val states = machine.analyze(jcMethod.method, targets) states.map { testResolver.resolve(jcMethod, it) } } } diff --git a/usvm-jvm/src/test/kotlin/org/usvm/samples/targets/TestNullPointerDereference.kt b/usvm-jvm/src/test/kotlin/org/usvm/samples/targets/TestNullPointerDereference.kt index 7628a12e22..f1326dc18c 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/samples/targets/TestNullPointerDereference.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/samples/targets/TestNullPointerDereference.kt @@ -1,38 +1,41 @@ package org.usvm.samples.targets -import io.mockk.internalSubstitute -import org.jacodb.api.ext.findClass -import org.jacodb.api.ext.toType import org.junit.jupiter.api.Test +import org.usvm.PathSelectionStrategy import org.usvm.UMachineOptions +import org.usvm.api.targets.JcExitTarget import org.usvm.api.targets.JcLocationTarget import org.usvm.api.targets.JcNullPointerDereferenceTarget -import org.usvm.machine.JcMachine -import org.usvm.samples.JacoDBContainer -import org.usvm.samples.samplesKey -import kotlin.reflect.jvm.javaMethod -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue +import org.usvm.samples.JavaMethodTestRunner +import org.usvm.test.util.checkers.eq +import org.usvm.util.getJcMethod -class TestNullPointerDereference { +class TestNullPointerDereference : JavaMethodTestRunner() { @Test fun `Null Pointer Dereference with intermediate target`() { - val cp = JacoDBContainer(samplesKey).cp - val declaringClassName = requireNotNull(NullPointerDereference::twoPathsToNPE.javaMethod?.declaringClass?.name) - val jcClass = cp.findClass(declaringClassName).toType() - val jcMethod = jcClass.declaredMethods.first { it.name == NullPointerDereference::twoPathsToNPE.name }.method + val jcMethod = cp.getJcMethod(NullPointerDereference::twoPathsToNPE) val target = JcLocationTarget(jcMethod, jcMethod.instList[5]).apply { - addChild(JcNullPointerDereferenceTarget(jcMethod, jcMethod.instList[8])) + addChild(JcNullPointerDereferenceTarget(jcMethod, jcMethod.instList[8])).apply { + addChild(JcExitTarget()) + } } - val states = JcMachine(cp, UMachineOptions()).use { machine -> - machine.reproduceTargets(jcMethod, listOf(target)) + withOptions( + UMachineOptions( + pathSelectionStrategies = listOf(PathSelectionStrategy.TARGETED), + stopOnTargetsReached = true + ) + ) { + withTargets(listOf(target)) { + // In fact, checking that the first state which was reported has reached the target + checkDiscoveredPropertiesWithExceptions( + NullPointerDereference::twoPathsToNPE, + eq(1), + { _, n, r -> n <= 100 && r.exceptionOrNull() is NullPointerException } + ) + } } - assertEquals(1, states.size) - assertNotNull(states.single().reversedPath.asSequence().singleOrNull { it == jcMethod.instList[8] }) - assertNotNull(states.single().reversedPath.asSequence().singleOrNull { it == jcMethod.instList[5] }) } } diff --git a/usvm-jvm/src/test/kotlin/org/usvm/util/Util.kt b/usvm-jvm/src/test/kotlin/org/usvm/util/Util.kt index b6f505901c..4245e57dcb 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/util/Util.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/util/Util.kt @@ -1,6 +1,12 @@ package org.usvm.util +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcMethod +import org.jacodb.api.ext.findClass +import org.jacodb.api.ext.toType import java.io.File +import kotlin.reflect.KFunction +import kotlin.reflect.jvm.javaMethod val allClasspath: List get() { @@ -14,4 +20,10 @@ private val classpath: List .toList() } +fun JcClasspath.getJcMethod(func: KFunction<*>): JcMethod { + val declaringClassName = requireNotNull(func.javaMethod?.declaringClass?.name) + val jcClass = findClass(declaringClassName).toType() + return jcClass.declaredMethods.first { it.name == func.name }.method +} + inline fun Result<*>.isException(): Boolean = exceptionOrNull() is T diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt index c139774ca7..a7c3e4604a 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt @@ -1,6 +1,7 @@ package org.usvm.machine import kotlinx.collections.immutable.persistentListOf +import org.usvm.StateCollectionStrategy import org.usvm.UContext import org.usvm.UMachine import org.usvm.UMachineOptions @@ -9,7 +10,12 @@ import org.usvm.language.Program import org.usvm.language.SampleType import org.usvm.language.Stmt import org.usvm.ps.createPathSelector -import org.usvm.statistics.* +import org.usvm.statistics.CompositeUMachineObserver +import org.usvm.statistics.CoverageStatistics +import org.usvm.statistics.TerminatedStateRemover +import org.usvm.statistics.UMachineObserver +import org.usvm.statistics.collectors.CoveredNewStatesCollector +import org.usvm.statistics.collectors.TargetsReachedStatesCollector import org.usvm.statistics.distances.DistanceStatistics import org.usvm.stopstrategies.createStopStrategy @@ -31,26 +37,37 @@ class SampleMachine( private val distanceStatistics = DistanceStatistics(applicationGraph) - fun analyze( - method: Method<*> - ): Collection { - val initialState = getInitialState(method) + fun analyze(method: Method<*>, targets: List = emptyList()): Collection { + val initialState = getInitialState(method, targets) val coverageStatistics: CoverageStatistics, Stmt, SampleState> = CoverageStatistics(setOf(method), applicationGraph) val pathSelector = createPathSelector( initialState, options, + applicationGraph, { coverageStatistics }, { distanceStatistics } ) - val statesCollector = CoveredNewStatesCollector(coverageStatistics) { it.exceptionRegister != null } - val stopStrategy = createStopStrategy(options, { coverageStatistics }, { statesCollector.collectedStates.size }) + val statesCollector = + when (options.stateCollectionStrategy) { + StateCollectionStrategy.COVERED_NEW -> CoveredNewStatesCollector(coverageStatistics) { + it.exceptionRegister != null + } + StateCollectionStrategy.REACHED_TARGET -> TargetsReachedStatesCollector() + } - val observers = mutableListOf>(coverageStatistics) + val stopStrategy = createStopStrategy( + options, + targets, + { coverageStatistics }, + { statesCollector.collectedStates.size } + ) + val observers = mutableListOf>(coverageStatistics) observers.add(TerminatedStateRemover()) + observers.addAll(targets) observers.add(statesCollector) run( @@ -64,8 +81,8 @@ class SampleMachine( return statesCollector.collectedStates.map { resultModelConverter.convert(it, method) } } - private fun getInitialState(method: Method<*>): SampleState = - SampleState(ctx).apply { + private fun getInitialState(method: Method<*>, targets: List): SampleState = + SampleState(ctx, targets = targets).apply { addEntryMethodCall(applicationGraph, method) val model = solver.emptyModel() models = persistentListOf(model) diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt index 014736f1cf..7f10b3434a 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt @@ -4,4 +4,4 @@ import org.usvm.UTarget import org.usvm.language.Method import org.usvm.language.Stmt -open class SampleTarget(method: Method<*>, statement: Stmt) : UTarget, Stmt, SampleTarget, SampleState>(method, statement) +open class SampleTarget(method: Method<*>, statement: Stmt) : UTarget, Stmt, SampleTarget, SampleState>(method to statement) diff --git a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt index e8ef6db352..99f9b350df 100644 --- a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt +++ b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt @@ -52,12 +52,11 @@ enum class PathSelectionStrategy { * graph. * States are selected randomly with distribution based on distance to uncovered instructions. */ - CLOSEST_TO_UNCOVERED_RANDOM -} - -enum class TargetReproductionPathSelectionStrategy { - DETERMINISTIC, - RANDOMIZED + CLOSEST_TO_UNCOVERED_RANDOM, + TARGETED, + TARGETED_RANDOM, + TARGETED_CALL_STACK_LOCAL, + TARGETED_CALL_STACK_LOCAL_RANDOM } enum class PathSelectorCombinationStrategy { @@ -88,6 +87,11 @@ enum class CoverageZone { TRANSITIVE } +enum class StateCollectionStrategy { + COVERED_NEW, + REACHED_TARGET +} + data class UMachineOptions( /** * State selection heuristics. @@ -102,6 +106,7 @@ data class UMachineOptions( * @see PathSelectorCombinationStrategy */ val pathSelectorCombinationStrategy: PathSelectorCombinationStrategy = PathSelectorCombinationStrategy.INTERLEAVED, + val stateCollectionStrategy: StateCollectionStrategy = StateCollectionStrategy.COVERED_NEW, /** * Seed used for random operations. */ @@ -139,21 +144,8 @@ data class UMachineOptions( /** * SMT solver type used for path constraint solving. */ - val solverType: SolverType = SolverType.Z3 -) + val solverType: SolverType = SolverType.Z3, -data class TargetReproductionOptions( - val pathSelectionStrategy: TargetReproductionPathSelectionStrategy = TargetReproductionPathSelectionStrategy.RANDOMIZED, - /** - * Seed used for random operations. - */ - val randomSeed: Long = 0, - /** - * Optional limit of symbolic execution steps to stop execution on. - */ - val stepLimit: ULong? = null, - /** - * Optional timeout in milliseconds to stop execution on. - */ - val timeoutMs: Long? = 20_000, + val stopOnTargetsReached: Boolean = false, + val targetSearchDepth: UInt = 0u ) From 26674b07f397333c384b9f50e224fc962be34e61 Mon Sep 17 00:00:00 2001 From: Maksim Parshin Date: Wed, 30 Aug 2023 19:11:43 +0300 Subject: [PATCH 04/17] Main merge fixes --- usvm-core/src/main/kotlin/org/usvm/Merging.kt | 2 +- usvm-core/src/main/kotlin/org/usvm/State.kt | 8 ++++---- usvm-core/src/main/kotlin/org/usvm/StepScope.kt | 6 ++---- usvm-core/src/main/kotlin/org/usvm/Targets.kt | 4 ++-- usvm-core/src/main/kotlin/org/usvm/api/EngineApi.kt | 4 ++-- usvm-core/src/main/kotlin/org/usvm/api/MockApi.kt | 13 ++++--------- .../main/kotlin/org/usvm/ps/PathSelectorFactory.kt | 8 ++++---- .../collectors/TargetsReachedStatesCollector.kt | 2 +- usvm-core/src/test/kotlin/org/usvm/TestUtil.kt | 2 +- .../api/collections/SymbolicCollectionTestBase.kt | 2 ++ 10 files changed, 23 insertions(+), 28 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/Merging.kt b/usvm-core/src/main/kotlin/org/usvm/Merging.kt index a5ecb63660..1b54c35b8b 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Merging.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Merging.kt @@ -7,7 +7,7 @@ interface UMerger { fun merge(left: Entity, right: Entity): Entity? } -open class UStateMerger> : UMerger { +open class UStateMerger> : UMerger { // Never merge for now override fun merge(left: State, right: State) = null } diff --git a/usvm-core/src/main/kotlin/org/usvm/State.kt b/usvm-core/src/main/kotlin/org/usvm/State.kt index 7d2b147e16..1cd6031612 100644 --- a/usvm-core/src/main/kotlin/org/usvm/State.kt +++ b/usvm-core/src/main/kotlin/org/usvm/State.kt @@ -55,7 +55,7 @@ abstract class UState + other as UState<*, *, *, *, *, *> return id == other.id } @@ -116,7 +116,7 @@ private const val OriginalState = false * forked state. * */ -private fun , Type, Field, Context : UContext> forkIfSat( +private fun , Type, Context : UContext> forkIfSat( state: T, newConstraintToOriginalState: UBoolExpr, newConstraintToForkedState: UBoolExpr, @@ -176,7 +176,7 @@ private fun , Type, Field, Context : UCont * 2. makes not more than one query to USolver; * 3. if both [condition] and ![condition] are satisfiable, then [ForkResult.positiveState] === [state]. */ -fun , Type, Field, Context : UContext> fork( +fun , Type, Context : UContext> fork( state: T, condition: UBoolExpr, ): ForkResult { @@ -237,7 +237,7 @@ fun , Type, Field, Context : UContext> for * @return a list of states for each condition - `null` state * means [UUnknownResult] or [UUnsatResult] of checking condition. */ -fun , Type, Field, Context : UContext> forkMulti( +fun , Type, Context : UContext> forkMulti( state: T, conditions: Iterable, ): List { diff --git a/usvm-core/src/main/kotlin/org/usvm/StepScope.kt b/usvm-core/src/main/kotlin/org/usvm/StepScope.kt index 6ca67948c4..4b695df566 100644 --- a/usvm-core/src/main/kotlin/org/usvm/StepScope.kt +++ b/usvm-core/src/main/kotlin/org/usvm/StepScope.kt @@ -1,8 +1,6 @@ package org.usvm -import org.usvm.StepScope.StepScopeState.CANNOT_BE_PROCESSED -import org.usvm.StepScope.StepScopeState.CAN_BE_PROCESSED -import org.usvm.StepScope.StepScopeState.DEAD +import org.usvm.StepScope.StepScopeState.* /** * An auxiliary class, which carefully maintains forks and asserts via [fork] and [assert]. @@ -18,7 +16,7 @@ import org.usvm.StepScope.StepScopeState.DEAD * * @param originalState an initial state. */ -class StepScope, Type, Field, Context : UContext>( +class StepScope, Type, Context : UContext>( private val originalState: T, ) { private val forkedStates = mutableListOf() diff --git a/usvm-core/src/main/kotlin/org/usvm/Targets.kt b/usvm-core/src/main/kotlin/org/usvm/Targets.kt index d0e349d84f..7a88b259cf 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Targets.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Targets.kt @@ -2,7 +2,7 @@ package org.usvm import org.usvm.statistics.UMachineObserver -abstract class UTarget, State : UState<*, *, Method, Statement, *, Target, State>>( +abstract class UTarget, State : UState<*, Method, Statement, *, Target, State>>( val location: Pair? = null ) : UMachineObserver { private val childrenImpl = mutableListOf() @@ -58,7 +58,7 @@ abstract class UTarget, State : UState<*, *, Method, Statement, *, Target, State>> : UTarget(null) { +class ExitTarget, State : UState<*, Method, Statement, *, Target, State>> : UTarget(null) { override fun onStateTerminated(state: State) { visit(state) super.onStateTerminated(state) diff --git a/usvm-core/src/main/kotlin/org/usvm/api/EngineApi.kt b/usvm-core/src/main/kotlin/org/usvm/api/EngineApi.kt index 60c5ea4fdc..7c1126db42 100644 --- a/usvm-core/src/main/kotlin/org/usvm/api/EngineApi.kt +++ b/usvm-core/src/main/kotlin/org/usvm/api/EngineApi.kt @@ -5,10 +5,10 @@ import org.usvm.UHeapRef import org.usvm.UState -fun UState<*, *, *, *, *>.assume(expr: UBoolExpr) { +fun UState<*, *, *, *, *, *>.assume(expr: UBoolExpr) { pathConstraints += expr } -fun UState<*, *, *, *, *>.objectTypeEquals(lhs: UHeapRef, rhs: UHeapRef): UBoolExpr { +fun UState<*, *, *, *, *, *>.objectTypeEquals(lhs: UHeapRef, rhs: UHeapRef): UBoolExpr { TODO("Objects types equality check: $lhs, $rhs") } diff --git a/usvm-core/src/main/kotlin/org/usvm/api/MockApi.kt b/usvm-core/src/main/kotlin/org/usvm/api/MockApi.kt index 68f558277b..6d821444be 100644 --- a/usvm-core/src/main/kotlin/org/usvm/api/MockApi.kt +++ b/usvm-core/src/main/kotlin/org/usvm/api/MockApi.kt @@ -1,22 +1,17 @@ package org.usvm.api -import org.usvm.UExpr -import org.usvm.UHeapRef -import org.usvm.USizeExpr -import org.usvm.USort -import org.usvm.UState -import org.usvm.uctx +import org.usvm.* // TODO: special mock api for variables -fun UState<*, Method, *, *, *>.makeSymbolicPrimitive( +fun UState<*, Method, *, *, *, *>.makeSymbolicPrimitive( sort: T ): UExpr { check(sort != sort.uctx.addressSort) { "$sort is not primitive" } return memory.mock { call(lastEnteredMethod, emptySequence(), sort) } } -fun UState.makeSymbolicRef(type: Type): UHeapRef { +fun UState.makeSymbolicRef(type: Type): UHeapRef { val ref = memory.mock { call(lastEnteredMethod, emptySequence(), memory.ctx.addressSort) } memory.types.addSubtype(ref, type) @@ -25,7 +20,7 @@ fun UState.makeSymbolicRef(type: Type): UH return ref } -fun UState.makeSymbolicArray(arrayType: Type, size: USizeExpr): UHeapRef { +fun UState.makeSymbolicArray(arrayType: Type, size: USizeExpr): UHeapRef { val ref = memory.mock { call(lastEnteredMethod, emptySequence(), memory.ctx.addressSort) } memory.types.addSubtype(ref, arrayType) diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt index f2ba517636..976cb660d0 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt @@ -118,9 +118,9 @@ private fun > UPathSelector.wrapIfRe this } -private fun > compareById(): Comparator = compareBy { it.id } +private fun > compareById(): Comparator = compareBy { it.id } -private fun > createDepthPathSelector(random: Random? = null): UPathSelector { +private fun > createDepthPathSelector(random: Random? = null): UPathSelector { if (random == null) { return WeightedPathSelector( priorityCollectionFactory = { DeterministicPriorityCollection(Comparator.naturalOrder()) }, @@ -177,7 +177,7 @@ private fun , State : UState<*, *, Method, Statement, *, Target, State>> createTargetedPathSelector( +internal fun , State : UState<*, Method, Statement, *, Target, State>> createTargetedPathSelector( distanceStatistics: DistanceStatistics, random: Random? = null, ): UPathSelector { @@ -224,7 +224,7 @@ internal fun InterprocDistance.logWeight(): UInt { return distance } -internal fun , State : UState<*, *, Method, Statement, *, Target, State>> createTargetedPathSelector( +internal fun , State : UState<*, Method, Statement, *, Target, State>> createTargetedPathSelector( distanceStatistics: DistanceStatistics, applicationGraph: ApplicationGraph, callGraphReachabilityStatistics: CallGraphReachabilityStatistics? = null, diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt index 2ebbe9aaad..df8d735070 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt @@ -2,7 +2,7 @@ package org.usvm.statistics.collectors import org.usvm.UState -class TargetsReachedStatesCollector> : StatesCollector { +class TargetsReachedStatesCollector> : StatesCollector { private val mutableCollectedStates = mutableListOf() override val collectedStates: List = mutableCollectedStates diff --git a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt index 3a965be34f..477feff03d 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt @@ -38,7 +38,7 @@ internal class TestTarget(method: String, offset: Int) : UTarget, pathConstraints: UPathConstraints, - memory: UMemoryBase, models: List>, + memory: UMemory, models: List>, pathLocation: PathsTrieNode, targetTrees: Collection = emptyList() ) : UState(ctx, callStack, pathConstraints, memory, models, pathLocation, targetTrees) { diff --git a/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt b/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt index 7f4590740b..c3373c09b6 100644 --- a/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt +++ b/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt @@ -51,6 +51,8 @@ abstract class SymbolicCollectionTestBase { scope = StepScope(StateStub(ctx, pathConstraints, memory)) } + class TargetStub : UTarget() + class StateStub( ctx: UContext, pathConstraints: UPathConstraints, From 3da2bf58d7c205b8764e7c0ff0dff522d34deab8 Mon Sep 17 00:00:00 2001 From: Maksim Parshin Date: Wed, 30 Aug 2023 20:53:51 +0300 Subject: [PATCH 05/17] Add comments --- usvm-core/src/main/kotlin/org/usvm/Context.kt | 2 +- usvm-core/src/main/kotlin/org/usvm/State.kt | 15 +++++- .../src/main/kotlin/org/usvm/StepScope.kt | 4 +- usvm-core/src/main/kotlin/org/usvm/Targets.kt | 46 +++++++++++++++---- .../src/main/kotlin/org/usvm/api/MockApi.kt | 7 ++- .../kotlin/org/usvm/ps/PathSelectorFactory.kt | 17 ++++++- .../statistics/collectors/StatesCollector.kt | 7 +++ .../TargetsReachedStatesCollector.kt | 4 ++ .../CallGraphReachabilityStatistics.kt | 10 ++++ .../distances/DistanceCalculator.kt | 15 ++++++ .../distances/InterprocDistanceCalculator.kt | 32 ++++++++++++- .../TargetsReachedStopStrategy.kt | 3 ++ .../usvm/ps/RandomTreePathSelectorTests.kt | 8 +++- .../InterprocDistanceCalculatorTests.kt | 11 ++++- .../org/usvm/api/targets/JcExitTarget.kt | 4 ++ .../org/usvm/api/targets/JcLocationTarget.kt | 3 ++ .../targets/JcNullPointerDereferenceTarget.kt | 3 ++ .../kotlin/org/usvm/api/targets/JcTarget.kt | 5 +- .../main/kotlin/org/usvm/machine/JcMachine.kt | 8 +++- .../interpreter/JcInterpreterObserver.kt | 8 +++- .../kotlin/org/usvm/machine/SampleTarget.kt | 5 +- .../main/kotlin/org/usvm/UMachineOptions.kt | 39 +++++++++++++++- .../kotlin/org/usvm/algorithms/GraphUtils.kt | 10 +++- .../main/kotlin/org/usvm/util/MathUtils.kt | 3 ++ 24 files changed, 242 insertions(+), 27 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/Context.kt b/usvm-core/src/main/kotlin/org/usvm/Context.kt index a1b4cc4edc..7a022c418c 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Context.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Context.kt @@ -349,7 +349,7 @@ open class UContext( private val initialLocation = RootNode() fun , Statement> mkInitialLocation() - : PathsTrieNode = initialLocation.uncheckedCast() + : PathsTrieNode = initialLocation.uncheckedCast() fun mkUValueSampler(): KSortVisitor> { return UValueSampler(this) diff --git a/usvm-core/src/main/kotlin/org/usvm/State.kt b/usvm-core/src/main/kotlin/org/usvm/State.kt index 1cd6031612..c6832912ce 100644 --- a/usvm-core/src/main/kotlin/org/usvm/State.kt +++ b/usvm-core/src/main/kotlin/org/usvm/State.kt @@ -78,10 +78,23 @@ abstract class UState() - // TODO: clean removed targets sometimes + /** + * Collection of state's current targets. + * TODO: clean removed targets sometimes + */ val targets: Collection get() = currentTargets.filterNot { it.isRemoved } + + /** + * Reached targets with no children. + */ val reachedSinks: Set = reachedSinksImpl + /** + * Tries to remove the [target] from current targets collection and + * add its children there. + * + * @return true if the [target] was successfully removed. + */ internal fun visitTarget(target: Target): Boolean { if (currentTargets.remove(target) && !target.isRemoved) { if (target.isSink) { diff --git a/usvm-core/src/main/kotlin/org/usvm/StepScope.kt b/usvm-core/src/main/kotlin/org/usvm/StepScope.kt index 4b695df566..f28efc9c8e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/StepScope.kt +++ b/usvm-core/src/main/kotlin/org/usvm/StepScope.kt @@ -1,6 +1,8 @@ package org.usvm -import org.usvm.StepScope.StepScopeState.* +import org.usvm.StepScope.StepScopeState.CANNOT_BE_PROCESSED +import org.usvm.StepScope.StepScopeState.CAN_BE_PROCESSED +import org.usvm.StepScope.StepScopeState.DEAD /** * An auxiliary class, which carefully maintains forks and asserts via [fork] and [assert]. diff --git a/usvm-core/src/main/kotlin/org/usvm/Targets.kt b/usvm-core/src/main/kotlin/org/usvm/Targets.kt index 7a88b259cf..fa1b46aed4 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Targets.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Targets.kt @@ -2,20 +2,49 @@ package org.usvm import org.usvm.statistics.UMachineObserver +/** + * Base class for a symbolic execution target. A target can be understood as a 'task' for symbolic machine + * which it tries to complete. For example, a task can have an attached location which should be visited by a state + * to consider the task completed. Also, the targets can produce some effects on states visiting them. + * + * Tasks can have 'child' tasks which should be completed only after its parent has been completed. For example, + * it allows to force the execution along the specific path. + * + * Targets are designed to be shared between all the symbolic execution states. Due to this, once there is + * a state which has reached the target which has no children, it is logically removed from the targets tree. + * The other states ignore such removed targets. + */ abstract class UTarget, State : UState<*, Method, Statement, *, Target, State>>( + /** + * Optional location of the target. + */ val location: Pair? = null ) : UMachineObserver { private val childrenImpl = mutableListOf() private var parent: Target? = null + /** + * List of the child targets which should be reached after this target. + */ val children: List = childrenImpl + /** + * True if this target has no children. + */ val isSink get() = childrenImpl.isEmpty() + /** + * True if this target is logically removed from the tree. + */ var isRemoved = false private set - // TODO: avoid possible recursion + /** + * Adds a child target to this target. + * TODO: avoid possible recursion + * + * @return this target (for convenient target tree building). + */ fun addChild(child: Target): Target { check(!isRemoved) { "Cannot add child to removed target" } require(child.parent == null) { "Cannot add child target with existing parent" } @@ -29,7 +58,13 @@ abstract class UTarget, State : UState<*, Method, Statement, *, Target, State>> : UTarget(null) { - override fun onStateTerminated(state: State) { - visit(state) - super.onStateTerminated(state) - } -} diff --git a/usvm-core/src/main/kotlin/org/usvm/api/MockApi.kt b/usvm-core/src/main/kotlin/org/usvm/api/MockApi.kt index 6d821444be..fd325abcaa 100644 --- a/usvm-core/src/main/kotlin/org/usvm/api/MockApi.kt +++ b/usvm-core/src/main/kotlin/org/usvm/api/MockApi.kt @@ -1,6 +1,11 @@ package org.usvm.api -import org.usvm.* +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.USort +import org.usvm.UState +import org.usvm.uctx // TODO: special mock api for variables diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt index 976cb660d0..19d8832824 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt @@ -1,9 +1,22 @@ package org.usvm.ps +import org.usvm.PathSelectionStrategy +import org.usvm.PathSelectorCombinationStrategy +import org.usvm.UMachineOptions +import org.usvm.UPathSelector +import org.usvm.UState +import org.usvm.UTarget import org.usvm.statistics.ApplicationGraph import org.usvm.statistics.CoverageStatistics -import org.usvm.algorithms.DeterministicPriorityCollection -import org.usvm.algorithms.RandomizedPriorityCollection +import org.usvm.statistics.distances.CallGraphReachabilityStatistics +import org.usvm.statistics.distances.CallStackDistanceCalculator +import org.usvm.statistics.distances.DistanceStatistics +import org.usvm.statistics.distances.DynamicTargetsShortestDistanceCalculator +import org.usvm.statistics.distances.InterprocDistance +import org.usvm.statistics.distances.InterprocDistanceCalculator +import org.usvm.statistics.distances.ReachabilityKind +import org.usvm.util.DeterministicPriorityCollection +import org.usvm.util.RandomizedPriorityCollection import org.usvm.util.log2 import kotlin.math.max import kotlin.random.Random diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/StatesCollector.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/StatesCollector.kt index 1e262e7404..ebe7b7fe8f 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/StatesCollector.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/StatesCollector.kt @@ -2,6 +2,13 @@ package org.usvm.statistics.collectors import org.usvm.statistics.UMachineObserver +/** + * Interface for [UMachineObserver]s which are able to + * collect states. + */ interface StatesCollector : UMachineObserver { + /** + * Current list of collected states. + */ val collectedStates: List } diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt index df8d735070..e8f1b4a904 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt @@ -2,6 +2,10 @@ package org.usvm.statistics.collectors import org.usvm.UState +/** + * [StatesCollector] implementation collecting only those states which have reached + * any sink targets. + */ class TargetsReachedStatesCollector> : StatesCollector { private val mutableCollectedStates = mutableListOf() override val collectedStates: List = mutableCollectedStates diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphReachabilityStatistics.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphReachabilityStatistics.kt index 4e29e4efaa..4a4b7e7434 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphReachabilityStatistics.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphReachabilityStatistics.kt @@ -4,6 +4,13 @@ import org.usvm.statistics.ApplicationGraph import org.usvm.util.limitedBfsTraversal import java.util.concurrent.ConcurrentHashMap +/** + * Calculates and caches information about reachability of one method from another + * in call graph. + * + * @param depthLimit methods which are reachable via paths longer than this value are + * not considered (i.e. 1 means that the target method should be directly called from source method). + */ class CallGraphReachabilityStatistics( private val depthLimit: UInt, private val applicationGraph: ApplicationGraph @@ -13,6 +20,9 @@ class CallGraphReachabilityStatistics( private fun getAdjacentVertices(vertex: Method): Sequence = applicationGraph.statementsOf(vertex).flatMap(applicationGraph::callees).distinct() + /** + * Checks if [methodTo] is reachable from [methodFrom] in call graph. + */ fun checkReachability(methodFrom: Method, methodTo: Method): Boolean = cache.computeIfAbsent(methodFrom) { limitedBfsTraversal(depthLimit, listOf(methodFrom), ::getAdjacentVertices).toSet() diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt index 43cd58de08..54890b5d40 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt @@ -2,10 +2,22 @@ package org.usvm.statistics.distances import org.usvm.UCallStack +/** + * @see calculateDistance + */ fun interface StaticTargetsDistanceCalculator { + + /** + * Calculate distance from location represented by [currentStatement] and [callStack] to + * some predefined targets. + */ fun calculateDistance(currentStatement: Statement, callStack: UCallStack): Distance } +/** + * Dynamically accumulates multiple [StaticTargetsDistanceCalculator] by their targets allowing + * to calculate distances to arbitrary targets. + */ class DynamicTargetsShortestDistanceCalculator( private val getDistanceCalculator: (Method, Statement) -> StaticTargetsDistanceCalculator ) { @@ -16,6 +28,9 @@ class DynamicTargetsShortestDistanceCalculator( return calculatorsByTarget.remove(target) != null } + /** + * Calculate distance from location represented by [currentStatement] and [callStack] to the [target]. + */ fun calculateDistance( currentStatement: Statement, callStack: UCallStack, diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt index 3308553c05..62e4e69191 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt @@ -3,21 +3,49 @@ package org.usvm.statistics.distances import org.usvm.UCallStack import org.usvm.statistics.ApplicationGraph +/** + * Kind of target reachability in application graph. + */ enum class ReachabilityKind { + /** + * Target is located in the same method and is locally reachable. + */ LOCAL, + + /** + * Target is reachable from some method which can be called later. + */ UP_STACK, + /** + * Target is reachable from some method on the call stack after returning to it. + */ DOWN_STACK, + /** + * Target is unreachable. + */ NONE } -// TODO: add more information about the path -// TODO: add new targets according to the path? data class InterprocDistance(val distance: UInt, val reachabilityKind: ReachabilityKind) { val isUnreachable = reachabilityKind == ReachabilityKind.NONE } +/** + * Calculates shortest distances from location (represented as statement and call stack) to the set of targets + * considering call graph reachability. + * + * @param targetLocation target to calculate distance to. + * @param applicationGraph application graph to calculate distances on. + * @param getCfgDistance function with the following signature: + * (method, stmtFrom, stmtTo) -> shortest CFG distance from stmtFrom to stmtTo. + * @param getCfgDistanceToExitPoint function with the following signature: + * (method, stmt) -> shortest CFG distance from stmt to any of method's exit points. + * @param checkReachabilityInCallGraph function with the following signature: + * (method1, method2) -> true if method2 is reachable from method1, false otherwise. + */ // TODO: calculate distance in blocks?? // TODO: give priority to paths without calls +// TODO: add new targets according to the path? internal class InterprocDistanceCalculator( private val targetLocation: Pair, private val applicationGraph: ApplicationGraph, diff --git a/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt index 4871ae4a98..11d7f750ff 100644 --- a/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt +++ b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt @@ -2,6 +2,9 @@ package org.usvm.stopstrategies import org.usvm.UTarget +/** + * A stop strategy which stops when all sinks in [targets] are reached. + */ class TargetsReachedStopStrategy(private val targets: Collection>) : StopStrategy { override fun shouldStop(): Boolean = targets.all { it.isRemoved } } diff --git a/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt b/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt index b58bfdbc1c..b40928c438 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt @@ -8,7 +8,13 @@ import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource -import org.usvm.* +import org.usvm.TestState +import org.usvm.UState +import org.usvm.pseudoRandom +import org.usvm.PathsTrieNode +import org.usvm.PathsTrieNodeImpl +import org.usvm.RootNode +import org.usvm.TestInstruction import kotlin.test.assertEquals internal class RandomTreePathSelectorTests { diff --git a/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt b/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt index 3c32b488ce..0a8927dc36 100644 --- a/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt @@ -3,8 +3,15 @@ package org.usvm.statistics import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource -import org.usvm.* -import org.usvm.statistics.distances.* +import org.usvm.TestInstruction +import org.usvm.UCallStack +import org.usvm.appGraph +import org.usvm.callStackOf +import org.usvm.statistics.distances.CallGraphReachabilityStatistics +import org.usvm.statistics.distances.DistanceStatistics +import org.usvm.statistics.distances.InterprocDistance +import org.usvm.statistics.distances.InterprocDistanceCalculator +import org.usvm.statistics.distances.ReachabilityKind import kotlin.test.assertEquals class InterprocDistanceCalculatorTests { diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcExitTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcExitTarget.kt index e86d8556c6..545c99d4b6 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcExitTarget.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcExitTarget.kt @@ -2,6 +2,10 @@ package org.usvm.api.targets import org.usvm.machine.state.JcState +/** + * [JcTarget] which is reached when a state is terminated. Can be used to + * force termination of the states, which have visited [JcExitTarget]'s parent targets. + */ class JcExitTarget : JcTarget() { override fun onStateTerminated(state: JcState) { visit(state) diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt index 1f376ee4d6..0a28b025b5 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt @@ -4,6 +4,9 @@ import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst import org.usvm.machine.state.JcState +/** + * [JcTarget] which is reached when location ([method], [inst]) is reached. + */ class JcLocationTarget(private val method: JcMethod, private val inst: JcInst) : JcTarget(method to inst) { private fun hasReached(state: JcState): Boolean { diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt index 9eb38cd435..9119256ca7 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt @@ -5,6 +5,9 @@ import org.jacodb.api.cfg.JcInst import org.usvm.UHeapRef import org.usvm.machine.state.JcState +/** + * [JcTarget] which is reached when null pointer is dereferenced in location ([method], [inst]). + */ class JcNullPointerDereferenceTarget(private val method: JcMethod, private val inst: JcInst) : JcTarget(method to inst) { override fun onNullPointerDereference(state: JcState, ref: UHeapRef) { diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt index f53401bafc..44e41412cf 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt @@ -7,7 +7,10 @@ import org.usvm.UTarget import org.usvm.machine.interpreter.JcInterpreterObserver import org.usvm.machine.state.JcState -open class JcTarget(location: Pair? = null +/** + * Base class for JcMachine targets. + */ +abstract class JcTarget(location: Pair? = null ) : UTarget(location), JcInterpreterObserver { override fun onNullPointerDereference(state: JcState, ref: UHeapRef) { diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt index bfdf1e6b08..b7084b96c7 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt @@ -15,7 +15,11 @@ import org.usvm.machine.state.JcMethodResult import org.usvm.machine.state.JcState import org.usvm.machine.state.lastStmt import org.usvm.ps.createPathSelector -import org.usvm.statistics.* +import org.usvm.statistics.CompositeUMachineObserver +import org.usvm.statistics.CoverageStatistics +import org.usvm.statistics.TerminatedStateRemover +import org.usvm.statistics.TransitiveCoverageZoneObserver +import org.usvm.statistics.UMachineObserver import org.usvm.statistics.collectors.CoveredNewStatesCollector import org.usvm.statistics.collectors.TargetsReachedStatesCollector import org.usvm.statistics.distances.DistanceStatistics @@ -38,7 +42,7 @@ class JcMachine( private val distanceStatistics = DistanceStatistics(applicationGraph) fun analyze(method: JcMethod, targets: List = emptyList()): List { - logger.debug("$this.analyze($method, $targets)") + logger.debug("{}.analyze({}, {})", this, method, targets) val initialState = interpreter.getInitialState(method, targets) val methodsToTrackCoverage = diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreterObserver.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreterObserver.kt index ee8a65254d..c3fb485a3e 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreterObserver.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreterObserver.kt @@ -2,9 +2,15 @@ package org.usvm.machine.interpreter import org.usvm.UHeapRef import org.usvm.machine.state.JcState -import org.usvm.statistics.UMachineObserver +/** + * JcInterpreter events observer. + */ interface JcInterpreterObserver { + + /** + * Called when [state] dereferences null pointer in [ref]. + */ fun onNullPointerDereference(state: JcState, ref: UHeapRef) { } } diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt index 7f10b3434a..2db8b01b80 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt @@ -4,4 +4,7 @@ import org.usvm.UTarget import org.usvm.language.Method import org.usvm.language.Stmt -open class SampleTarget(method: Method<*>, statement: Stmt) : UTarget, Stmt, SampleTarget, SampleState>(method to statement) +/** + * Base class for SampleMachine targets. + */ +abstract class SampleTarget(method: Method<*>, statement: Stmt) : UTarget, Stmt, SampleTarget, SampleState>(method to statement) diff --git a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt index 99f9b350df..a0c31ad983 100644 --- a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt +++ b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt @@ -53,9 +53,30 @@ enum class PathSelectionStrategy { * States are selected randomly with distribution based on distance to uncovered instructions. */ CLOSEST_TO_UNCOVERED_RANDOM, + + /** + * Gives priority to the states which are closer to their targets considering interprocedural + * reachability. + * The closest to targets state is always selected. + */ TARGETED, + /** + * Gives priority to the states which are closer to their targets considering interprocedural + * reachability. + * States are selected randomly with distribution based on distance to targets. + */ TARGETED_RANDOM, + /** + * Gives priority to the states which are closer to their targets considering only current call stack + * reachability. + * The closest to targets state is always selected. + */ TARGETED_CALL_STACK_LOCAL, + /** + * Gives priority to the states which are closer to their targets considering only current call stack + * reachability. + * States are selected randomly with distribution based on distance to targets. + */ TARGETED_CALL_STACK_LOCAL_RANDOM } @@ -88,7 +109,13 @@ enum class CoverageZone { } enum class StateCollectionStrategy { + /** + * Collect only those terminated states which have covered new locations. + */ COVERED_NEW, + /** + * Collect only those states which have reached sink targets. + */ REACHED_TARGET } @@ -106,6 +133,11 @@ data class UMachineOptions( * @see PathSelectorCombinationStrategy */ val pathSelectorCombinationStrategy: PathSelectorCombinationStrategy = PathSelectorCombinationStrategy.INTERLEAVED, + /** + * Strategy to collect terminated states. + * + * @see StateCollectionStrategy + */ val stateCollectionStrategy: StateCollectionStrategy = StateCollectionStrategy.COVERED_NEW, /** * Seed used for random operations. @@ -145,7 +177,12 @@ data class UMachineOptions( * SMT solver type used for path constraint solving. */ val solverType: SolverType = SolverType.Z3, - + /** + * Should machine stop when all sink targets are reached. + */ val stopOnTargetsReached: Boolean = false, + /** + * Depth of the interprocedural reachability search used in distance-based path selectors. + */ val targetSearchDepth: UInt = 0u ) diff --git a/usvm-util/src/main/kotlin/org/usvm/algorithms/GraphUtils.kt b/usvm-util/src/main/kotlin/org/usvm/algorithms/GraphUtils.kt index e147b8e5d1..493f31e760 100644 --- a/usvm-util/src/main/kotlin/org/usvm/algorithms/GraphUtils.kt +++ b/usvm-util/src/main/kotlin/org/usvm/algorithms/GraphUtils.kt @@ -74,7 +74,7 @@ inline fun findMinDistancesInUnweightedGraph( * Returns the sequence of vertices in breadth-first order. * * @param startVertices vertices to start traversal from. - * @param adjacentVertices function which maps a vertex to the sequence of vertices adjacent to + * @param adjacentVertices function which maps a vertex to the sequence of vertices adjacent to. * it. */ inline fun bfsTraversal(startVertices: Collection, crossinline adjacentVertices: (V) -> Sequence): Sequence { @@ -91,6 +91,14 @@ inline fun bfsTraversal(startVertices: Collection, crossinline adjacentVe } } +/** + * Returns the sequence of vertices with depth <= [depthLimit] in breadth-first order. + * + * @param depthLimit vertices which are reachable via paths longer than this value are + * not considered (i.e. 1 means only the vertices adjacent to start). + * @param startVertices vertices to start traversal from. + * @param adjacentVertices function which maps a vertex to the sequence of vertices adjacent to. + */ fun limitedBfsTraversal(depthLimit: UInt, startVertices: Collection, adjacentVertices: (V) -> Sequence): Sequence { var currentDepth = 0u var numberOfVerticesOfCurrentLevel = startVertices.size diff --git a/usvm-util/src/main/kotlin/org/usvm/util/MathUtils.kt b/usvm-util/src/main/kotlin/org/usvm/util/MathUtils.kt index 700e63d17b..ed7315476f 100644 --- a/usvm-util/src/main/kotlin/org/usvm/util/MathUtils.kt +++ b/usvm-util/src/main/kotlin/org/usvm/util/MathUtils.kt @@ -1,5 +1,8 @@ package org.usvm.util +/** + * Unsigned integer logarithm base 2. Zero evaluates to zero. + */ fun log2(n: UInt): UInt { if (n == UInt.MAX_VALUE) { return 32u From 9f8b1a61e7bab613bdce0acfdbd3371f10489ad7 Mon Sep 17 00:00:00 2001 From: Maksim Parshin Date: Mon, 4 Sep 2023 17:36:50 +0300 Subject: [PATCH 06/17] Make targets persistent --- usvm-core/src/main/kotlin/org/usvm/State.kt | 25 +++++++++++-------- .../kotlin/org/usvm/machine/state/JcState.kt | 2 +- .../kotlin/org/usvm/machine/SampleState.kt | 2 +- .../main/kotlin/org/usvm/util/MathUtils.kt | 11 +++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/State.kt b/usvm-core/src/main/kotlin/org/usvm/State.kt index c6832912ce..c1a0514e33 100644 --- a/usvm-core/src/main/kotlin/org/usvm/State.kt +++ b/usvm-core/src/main/kotlin/org/usvm/State.kt @@ -1,6 +1,7 @@ package org.usvm import io.ksmt.expr.KInterpretedValue +import kotlinx.collections.immutable.toPersistentList import org.usvm.constraints.UPathConstraints import org.usvm.memory.UMemory import org.usvm.model.UModelBase @@ -18,7 +19,7 @@ abstract class UState, open var models: List>, open var pathLocation: PathsTrieNode, - targets: Collection = emptyList() + targets: List = emptyList() ) { /** * Deterministic state id. @@ -75,14 +76,16 @@ abstract class UState() /** * Collection of state's current targets. * TODO: clean removed targets sometimes */ - val targets: Collection get() = currentTargets.filterNot { it.isRemoved } + val targets: Sequence get() = targetsImpl.asSequence().filterNot { it.isRemoved } /** * Reached targets with no children. @@ -96,15 +99,17 @@ abstract class UState 1u) { - m = m shr 1 - ret++ - } - return ret + return 31u - Integer.numberOfLeadingZeros(n.toInt()).toUInt() } From c7a63cfc3cb6b4ed34e042e5b838a07173c8baec Mon Sep 17 00:00:00 2001 From: Maksim Parshin Date: Wed, 6 Sep 2023 09:38:54 +0300 Subject: [PATCH 07/17] Distance statistics refactoring --- .../kotlin/org/usvm/ps/PathSelectorFactory.kt | 53 ++++++++--------- .../CallGraphReachabilityStatistics.kt | 30 ---------- .../distances/CallGraphStatistics.kt | 12 ++++ .../distances/CallGraphStatisticsImpl.kt | 29 ++++++++++ .../distances/CallStackDistanceCalculator.kt | 16 +++--- .../statistics/distances/CfgStatistics.kt | 17 ++++++ ...anceStatistics.kt => CfgStatisticsImpl.kt} | 22 +++---- .../distances/InterprocDistanceCalculator.kt | 21 +++---- .../distances/PlainCallGraphStatistics.kt | 10 ++++ .../src/test/kotlin/org/usvm/TestUtil.kt | 2 +- .../CallStackDistanceCalculatorTests.kt | 43 ++++++++------ .../InterprocDistanceCalculatorTests.kt | 22 ++++--- .../org/usvm/machine/JcCallGraphStatistics.kt | 57 +++++++++++++++++++ .../main/kotlin/org/usvm/machine/JcMachine.kt | 19 ++++++- .../kotlin/org/usvm/machine/SampleMachine.kt | 18 +++++- 15 files changed, 244 insertions(+), 127 deletions(-) delete mode 100644 usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphReachabilityStatistics.kt create mode 100644 usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatistics.kt create mode 100644 usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatisticsImpl.kt create mode 100644 usvm-core/src/main/kotlin/org/usvm/statistics/distances/CfgStatistics.kt rename usvm-core/src/main/kotlin/org/usvm/statistics/distances/{DistanceStatistics.kt => CfgStatisticsImpl.kt} (54%) create mode 100644 usvm-core/src/main/kotlin/org/usvm/statistics/distances/PlainCallGraphStatistics.kt create mode 100644 usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt index 19d8832824..b1977b2b2e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt @@ -1,5 +1,7 @@ package org.usvm.ps +import kotlin.math.max +import kotlin.random.Random import org.usvm.PathSelectionStrategy import org.usvm.PathSelectorCombinationStrategy import org.usvm.UMachineOptions @@ -8,9 +10,9 @@ import org.usvm.UState import org.usvm.UTarget import org.usvm.statistics.ApplicationGraph import org.usvm.statistics.CoverageStatistics -import org.usvm.statistics.distances.CallGraphReachabilityStatistics +import org.usvm.statistics.distances.CallGraphStatistics import org.usvm.statistics.distances.CallStackDistanceCalculator -import org.usvm.statistics.distances.DistanceStatistics +import org.usvm.statistics.distances.CfgStatistics import org.usvm.statistics.distances.DynamicTargetsShortestDistanceCalculator import org.usvm.statistics.distances.InterprocDistance import org.usvm.statistics.distances.InterprocDistanceCalculator @@ -18,26 +20,20 @@ import org.usvm.statistics.distances.ReachabilityKind import org.usvm.util.DeterministicPriorityCollection import org.usvm.util.RandomizedPriorityCollection import org.usvm.util.log2 -import kotlin.math.max -import kotlin.random.Random fun , State : UState<*, Method, Statement, *, Target, State>> createPathSelector( initialState: State, options: UMachineOptions, applicationGraph: ApplicationGraph, coverageStatistics: () -> CoverageStatistics? = { null }, - distanceStatistics: () -> DistanceStatistics? = { null } + cfgStatistics: () -> CfgStatistics? = { null }, + callGraphStatistics: () -> CallGraphStatistics? = { null } ): UPathSelector { val strategies = options.pathSelectionStrategies require(strategies.isNotEmpty()) { "At least one path selector strategy should be specified" } val random by lazy { Random(options.randomSeed) } - val callGraphReachabilityStatistics = - if (options.targetSearchDepth > 0u) { - CallGraphReachabilityStatistics(options.targetSearchDepth, applicationGraph) - } else null - val selectors = strategies.map { strategy -> when (strategy) { PathSelectionStrategy.BFS -> BfsPathSelector() @@ -57,31 +53,31 @@ fun , Stat PathSelectionStrategy.CLOSEST_TO_UNCOVERED -> createClosestToUncoveredPathSelector( requireNotNull(coverageStatistics()) { "Coverage statistics is required for closest to uncovered path selector" }, - requireNotNull(distanceStatistics()) { "Distance statistics is required for closest to uncovered path selector" } + requireNotNull(cfgStatistics()) { "CFG statistics is required for closest to uncovered path selector" } ) PathSelectionStrategy.CLOSEST_TO_UNCOVERED_RANDOM -> createClosestToUncoveredPathSelector( requireNotNull(coverageStatistics()) { "Coverage statistics is required for closest to uncovered path selector" }, - requireNotNull(distanceStatistics()) { "Distance statistics is required for closest to uncovered path selector" }, + requireNotNull(cfgStatistics()) { "CFG statistics is required for closest to uncovered path selector" }, random ) PathSelectionStrategy.TARGETED -> createTargetedPathSelector( - requireNotNull(distanceStatistics()) { "Distance statistics is required for targeted path selector" }, - applicationGraph, - callGraphReachabilityStatistics + requireNotNull(cfgStatistics()) { "CFG statistics is required for targeted path selector" }, + requireNotNull(callGraphStatistics()) { "Call graph statistics is required for targeted path selector" }, + applicationGraph ) PathSelectionStrategy.TARGETED_RANDOM -> createTargetedPathSelector( - requireNotNull(distanceStatistics()) { "Distance statistics is required for targeted path selector" }, + requireNotNull(cfgStatistics()) { "CFG statistics is required for targeted path selector" }, + requireNotNull(callGraphStatistics()) { "Call graph statistics is required for targeted path selector" }, applicationGraph, - callGraphReachabilityStatistics, random ) PathSelectionStrategy.TARGETED_CALL_STACK_LOCAL -> createTargetedPathSelector( - requireNotNull(distanceStatistics()) { "Distance statistics is required for targeted call stack local path selector" } + requireNotNull(cfgStatistics()) { "CFG statistics is required for targeted call stack local path selector" } ) PathSelectionStrategy.TARGETED_CALL_STACK_LOCAL_RANDOM -> createTargetedPathSelector( - requireNotNull(distanceStatistics()) { "Distance statistics is required for targeted call stack local path selector" }, + requireNotNull(cfgStatistics()) { "CFG statistics is required for targeted call stack local path selector" }, random ) } @@ -150,13 +146,12 @@ private fun > createDepthPathSelector(rando private fun > createClosestToUncoveredPathSelector( coverageStatistics: CoverageStatistics, - distanceStatistics: DistanceStatistics, + cfgStatistics: CfgStatistics, random: Random? = null, ): UPathSelector { val distanceCalculator = CallStackDistanceCalculator( targets = coverageStatistics.getUncoveredStatements(), - getCfgDistance = distanceStatistics::getShortestCfgDistance, - getCfgDistanceToExitPoint = distanceStatistics::getShortestCfgDistanceToExitPoint + cfgStatistics = cfgStatistics ) coverageStatistics.addOnCoveredObserver { _, method, statement -> distanceCalculator.removeTarget(method, statement) } @@ -191,14 +186,13 @@ private fun , State : UState<*, Method, Statement, *, Target, State>> createTargetedPathSelector( - distanceStatistics: DistanceStatistics, + cfgStatistics: CfgStatistics, random: Random? = null, ): UPathSelector { val distanceCalculator = DynamicTargetsShortestDistanceCalculator { m, s -> CallStackDistanceCalculator( targets = listOf(m to s), - getCfgDistance = distanceStatistics::getShortestCfgDistance, - getCfgDistanceToExitPoint = distanceStatistics::getShortestCfgDistanceToExitPoint + cfgStatistics = cfgStatistics ) } @@ -238,18 +232,17 @@ internal fun InterprocDistance.logWeight(): UInt { } internal fun , State : UState<*, Method, Statement, *, Target, State>> createTargetedPathSelector( - distanceStatistics: DistanceStatistics, + cfgStatistics: CfgStatistics, + callGraphStatistics: CallGraphStatistics, applicationGraph: ApplicationGraph, - callGraphReachabilityStatistics: CallGraphReachabilityStatistics? = null, random: Random? = null, ): UPathSelector { val distanceCalculator = DynamicTargetsShortestDistanceCalculator { m, s -> InterprocDistanceCalculator( targetLocation = m to s, applicationGraph = applicationGraph, - getCfgDistance = distanceStatistics::getShortestCfgDistance, - getCfgDistanceToExitPoint = distanceStatistics::getShortestCfgDistanceToExitPoint, - checkReachabilityInCallGraph = if (callGraphReachabilityStatistics != null) (callGraphReachabilityStatistics::checkReachability) else { m1, m2 -> m1 == m2 } + cfgStatistics = cfgStatistics, + callGraphStatistics = callGraphStatistics ) } diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphReachabilityStatistics.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphReachabilityStatistics.kt deleted file mode 100644 index 4a4b7e7434..0000000000 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphReachabilityStatistics.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.usvm.statistics.distances - -import org.usvm.statistics.ApplicationGraph -import org.usvm.util.limitedBfsTraversal -import java.util.concurrent.ConcurrentHashMap - -/** - * Calculates and caches information about reachability of one method from another - * in call graph. - * - * @param depthLimit methods which are reachable via paths longer than this value are - * not considered (i.e. 1 means that the target method should be directly called from source method). - */ -class CallGraphReachabilityStatistics( - private val depthLimit: UInt, - private val applicationGraph: ApplicationGraph -) { - private val cache = ConcurrentHashMap>() - - private fun getAdjacentVertices(vertex: Method): Sequence = - applicationGraph.statementsOf(vertex).flatMap(applicationGraph::callees).distinct() - - /** - * Checks if [methodTo] is reachable from [methodFrom] in call graph. - */ - fun checkReachability(methodFrom: Method, methodTo: Method): Boolean = - cache.computeIfAbsent(methodFrom) { - limitedBfsTraversal(depthLimit, listOf(methodFrom), ::getAdjacentVertices).toSet() - }.contains(methodTo) -} diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatistics.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatistics.kt new file mode 100644 index 0000000000..f23fe3bf75 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatistics.kt @@ -0,0 +1,12 @@ +package org.usvm.statistics.distances + +/** + * Calculates call graph metrics. + */ +interface CallGraphStatistics { + + /** + * Checks if [methodTo] is reachable from [methodFrom] in call graph. + */ + fun checkReachability(methodFrom: Method, methodTo: Method): Boolean +} diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatisticsImpl.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatisticsImpl.kt new file mode 100644 index 0000000000..34bd465f51 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatisticsImpl.kt @@ -0,0 +1,29 @@ +package org.usvm.statistics.distances + +import java.util.concurrent.ConcurrentHashMap +import org.usvm.statistics.ApplicationGraph +import org.usvm.util.limitedBfsTraversal + +/** + * [CallGraphStatistics] common implementation with thread-safe results caching. As it is language-agnostic, + * it uses only [applicationGraph] info and **doesn't** consider potential virtual calls. + * + * @param depthLimit depthLimit methods which are reachable via paths longer than this value are + * not considered (i.e. 1 means that the target method should be directly called from source method). + * @param applicationGraph [ApplicationGraph] used to get callees info. + */ +class CallGraphStatisticsImpl( + private val depthLimit: UInt, + private val applicationGraph: ApplicationGraph +) : CallGraphStatistics { + + private val cache = ConcurrentHashMap>() + + private fun getCallees(method: Method): Sequence = + applicationGraph.statementsOf(method).flatMap(applicationGraph::callees).distinct() + + override fun checkReachability(methodFrom: Method, methodTo: Method): Boolean = + cache.computeIfAbsent(methodFrom) { + limitedBfsTraversal(depthLimit, listOf(methodFrom), ::getCallees).toSet() + }.contains(methodTo) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt index 62f511c58e..8cece0dbbc 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt @@ -1,7 +1,7 @@ package org.usvm.statistics.distances -import org.usvm.UCallStack import kotlin.math.min +import org.usvm.UCallStack /** * Calculates shortest distances from location (represented as statement and call stack) to the set of targets @@ -11,15 +11,11 @@ import kotlin.math.min * cached while the targets of the method remain the same. * * @param targets initial collection of targets. - * @param getCfgDistance function with the following signature: - * (method, stmtFrom, stmtTo) -> shortest CFG distance from stmtFrom to stmtTo. - * @param getCfgDistanceToExitPoint function with the following signature: - * (method, stmt) -> shortest CFG distance from stmt to any of method's exit points. + * @param cfgStatistics [CfgStatistics] instance used to calculate local distances on each frame. */ class CallStackDistanceCalculator( targets: Collection>, - private val getCfgDistance: (Method, Statement, Statement) -> UInt, - private val getCfgDistanceToExitPoint: (Method, Statement) -> UInt + private val cfgStatistics: CfgStatistics ) : StaticTargetsDistanceCalculator { // TODO: optimize for single target case @@ -35,7 +31,9 @@ class CallStackDistanceCalculator( private fun getMinDistanceToTargetInCurrentFrame(method: Method, statement: Statement): UInt { return minLocalDistanceToTargetCache.computeIfAbsent(method) { HashMap() } - .computeIfAbsent(statement) { targetsByMethod[method]?.minOfOrNull { getCfgDistance(method, statement, it) } ?: UInt.MAX_VALUE } + .computeIfAbsent(statement) { + targetsByMethod[method]?.minOfOrNull { cfgStatistics.getShortestDistance(method, statement, it) } ?: UInt.MAX_VALUE + } } fun addTarget(method: Method, statement: Statement): Boolean { @@ -73,7 +71,7 @@ class CallStackDistanceCalculator( checkNotNull(returnSite) { "Not first call stack frame had null return site" } } else currentStatement - val minDistanceToReturn = getCfgDistanceToExitPoint(method, locationInMethod) + val minDistanceToReturn = cfgStatistics.getShortestDistanceToExit(method, locationInMethod) val minDistanceToTargetInCurrentFrame = getMinDistanceToTargetInCurrentFrame(method, locationInMethod) val minDistanceToTargetInPreviousFrames = diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CfgStatistics.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CfgStatistics.kt new file mode 100644 index 0000000000..7902e47e18 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CfgStatistics.kt @@ -0,0 +1,17 @@ +package org.usvm.statistics.distances + +/** + * Calculates CFG metrics. + */ +interface CfgStatistics { + + /** + * Returns shortest CFG distance from [stmtFrom] to [stmtTo] located in [method]. + */ + fun getShortestDistance(method: Method, stmtFrom: Statement, stmtTo: Statement): UInt + + /** + * Returns CFG distance from [stmtFrom] to the closest exit point of [method]. + */ + fun getShortestDistanceToExit(method: Method, stmtFrom: Statement): UInt +} diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceStatistics.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CfgStatisticsImpl.kt similarity index 54% rename from usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceStatistics.kt rename to usvm-core/src/main/kotlin/org/usvm/statistics/distances/CfgStatisticsImpl.kt index c2646d1c10..f449fad1b8 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceStatistics.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CfgStatisticsImpl.kt @@ -1,34 +1,34 @@ package org.usvm.statistics.distances +import java.util.concurrent.ConcurrentHashMap import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.toImmutableMap import org.usvm.statistics.ApplicationGraph import org.usvm.algorithms.findMinDistancesInUnweightedGraph -import java.util.concurrent.ConcurrentHashMap /** - * Calculates distances in CFG and caches them. - * - * Operations are thread-safe. + * Common [CfgStatistics] implementation with thread-safe results caching. * * @param applicationGraph [ApplicationGraph] instance to get CFG from. */ -class DistanceStatistics(private val applicationGraph: ApplicationGraph) { +class CfgStatisticsImpl( + private val applicationGraph: ApplicationGraph +) : CfgStatistics { - private val allToAllShortestCfgDistanceCache = ConcurrentHashMap>>() - private val shortestCfgDistanceToExitPointCache = ConcurrentHashMap>() + private val allToAllShortestDistanceCache = ConcurrentHashMap>>() + private val shortestDistanceToExitCache = ConcurrentHashMap>() private fun getAllShortestCfgDistances(method: Method, stmtFrom: Statement): ImmutableMap { - val methodCache = allToAllShortestCfgDistanceCache.computeIfAbsent(method) { ConcurrentHashMap() } + val methodCache = allToAllShortestDistanceCache.computeIfAbsent(method) { ConcurrentHashMap() } return methodCache.computeIfAbsent(stmtFrom) { findMinDistancesInUnweightedGraph(stmtFrom, applicationGraph::successors, methodCache).toImmutableMap() } } - fun getShortestCfgDistance(method: Method, stmtFrom: Statement, stmtTo: Statement): UInt { + override fun getShortestDistance(method: Method, stmtFrom: Statement, stmtTo: Statement): UInt { return getAllShortestCfgDistances(method, stmtFrom)[stmtTo] ?: UInt.MAX_VALUE } - fun getShortestCfgDistanceToExitPoint(method: Method, stmtFrom: Statement): UInt { - return shortestCfgDistanceToExitPointCache.computeIfAbsent(method) { ConcurrentHashMap() } + override fun getShortestDistanceToExit(method: Method, stmtFrom: Statement): UInt { + return shortestDistanceToExitCache.computeIfAbsent(method) { ConcurrentHashMap() } .computeIfAbsent(stmtFrom) { val exitPoints = applicationGraph.exitPoints(method).toHashSet() getAllShortestCfgDistances(method, stmtFrom).filterKeys(exitPoints::contains).minByOrNull { it.value }?.value ?: UInt.MAX_VALUE diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt index 62e4e69191..22fe839257 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt @@ -36,12 +36,8 @@ data class InterprocDistance(val distance: UInt, val reachabilityKind: Reachabil * * @param targetLocation target to calculate distance to. * @param applicationGraph application graph to calculate distances on. - * @param getCfgDistance function with the following signature: - * (method, stmtFrom, stmtTo) -> shortest CFG distance from stmtFrom to stmtTo. - * @param getCfgDistanceToExitPoint function with the following signature: - * (method, stmt) -> shortest CFG distance from stmt to any of method's exit points. - * @param checkReachabilityInCallGraph function with the following signature: - * (method1, method2) -> true if method2 is reachable from method1, false otherwise. + * @param cfgStatistics [CfgStatistics] instance used to calculate local distances. + * @param callGraphStatistics [CallGraphStatistics] instance used to check call graph reachability. */ // TODO: calculate distance in blocks?? // TODO: give priority to paths without calls @@ -49,16 +45,15 @@ data class InterprocDistance(val distance: UInt, val reachabilityKind: Reachabil internal class InterprocDistanceCalculator( private val targetLocation: Pair, private val applicationGraph: ApplicationGraph, - private val getCfgDistance: (Method, Statement, Statement) -> UInt, - private val getCfgDistanceToExitPoint: (Method, Statement) -> UInt, - private val checkReachabilityInCallGraph: (Method, Method) -> Boolean + private val cfgStatistics: CfgStatistics, + private val callGraphStatistics: CallGraphStatistics ) : StaticTargetsDistanceCalculator { private val frameDistanceCache = HashMap>() private fun calculateFrameDistance(method: Method, statement: Statement): Pair { if (method == targetLocation.first) { - val localDistance = getCfgDistance(method, statement, targetLocation.second) + val localDistance = cfgStatistics.getShortestDistance(method, statement, targetLocation.second) if (localDistance != UInt.MAX_VALUE) { return localDistance to true } @@ -75,12 +70,12 @@ internal class InterprocDistanceCalculator( continue } - val distanceToCall = getCfgDistance(method, statement, statementOfMethod) + val distanceToCall = cfgStatistics.getShortestDistance(method, statement, statementOfMethod) if (distanceToCall >= minDistanceToCall) { continue } - if (applicationGraph.callees(statementOfMethod).any { checkReachabilityInCallGraph(it, targetLocation.first) }) { + if (applicationGraph.callees(statementOfMethod).any { callGraphStatistics.checkReachability(it, targetLocation.first) }) { minDistanceToCall = distanceToCall } } @@ -106,7 +101,7 @@ internal class InterprocDistanceCalculator( checkNotNull(statementOnCallStack) { "Not first call stack frame had null return site" } if (applicationGraph.successors(statementOnCallStack).any { calculateFrameDistance(methodOnCallStack, it).first != UInt.MAX_VALUE }) { - return InterprocDistance(getCfgDistanceToExitPoint(lastMethod, currentStatement), ReachabilityKind.DOWN_STACK) + return InterprocDistance(cfgStatistics.getShortestDistanceToExit(lastMethod, currentStatement), ReachabilityKind.DOWN_STACK) } statementOnCallStack = returnSite diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/PlainCallGraphStatistics.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/PlainCallGraphStatistics.kt new file mode 100644 index 0000000000..55b4410b35 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/PlainCallGraphStatistics.kt @@ -0,0 +1,10 @@ +package org.usvm.statistics.distances + +/** + * Limit case [CallGraphStatistics] implementation which considers two methods reachable + * only if they are the same. + */ +class PlainCallGraphStatistics : CallGraphStatistics { + + override fun checkReachability(methodFrom: Method, methodTo: Method): Boolean = methodFrom == methodTo +} diff --git a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt index 477feff03d..e45125b72c 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt @@ -40,7 +40,7 @@ internal class TestState( callStack: UCallStack, pathConstraints: UPathConstraints, memory: UMemory, models: List>, pathLocation: PathsTrieNode, - targetTrees: Collection = emptyList() + targetTrees: List = emptyList() ) : UState(ctx, callStack, pathConstraints, memory, models, pathLocation, targetTrees) { override fun clone(newConstraints: UPathConstraints?): TestState = this diff --git a/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt b/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt index 9509551012..d34eaab0af 100644 --- a/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt @@ -3,6 +3,7 @@ package org.usvm.statistics import org.junit.jupiter.api.Test import org.usvm.UCallStack import org.usvm.statistics.distances.CallStackDistanceCalculator +import org.usvm.statistics.distances.CfgStatistics import kotlin.test.assertEquals @OptIn(ExperimentalUnsignedTypes::class) @@ -41,23 +42,27 @@ internal class CallStackDistanceCalculatorTests { @Test fun smokeTest() { - fun getCfgDistance(method: String, from: Int, to: Int): UInt { - require(method == "A") - require(from == 1) - return when (to) { - 2 -> 1u - 3 -> 2u - 4 -> 1u - 5 -> 3u - 6 -> 7u - else -> UInt.MAX_VALUE + val cfgStatistics = object : CfgStatistics { + override fun getShortestDistance(method: String, stmtFrom: Int, stmtTo: Int): UInt { + require(method == "A") + require(stmtFrom == 1) + return when (stmtTo) { + 2 -> 1u + 3 -> 2u + 4 -> 1u + 5 -> 3u + 6 -> 7u + else -> UInt.MAX_VALUE + } } + + override fun getShortestDistanceToExit(method: String, stmtFrom: Int): UInt = 1u } val calculator = CallStackDistanceCalculator( setOf("A" to 2, "A" to 3, "A" to 4, "A" to 5, "A" to 6), - ::getCfgDistance - ) { _, _ -> 1u } + cfgStatistics + ) val currentStatement = 1 val callStack = UCallStack("A") @@ -77,12 +82,14 @@ internal class CallStackDistanceCalculatorTests { @Test fun multipleFrameDistanceTest() { - fun getCfgDistance(method: String, from: Int, to: Int): UInt { - return shortestDistances.getValue(method)[from][to] - } + val cfgStatistics = object : CfgStatistics { + override fun getShortestDistance(method: String, stmtFrom: Int, stmtTo: Int): UInt { + return shortestDistances.getValue(method)[stmtFrom][stmtTo] + } - fun getCfgDistanceToExitPoint(method: String, from: Int): UInt { - return shortestDistances.getValue(method)[from][0] + override fun getShortestDistanceToExit(method: String, stmtFrom: Int): UInt { + return shortestDistances.getValue(method)[stmtFrom][0] + } } var currentStatment = 3 @@ -91,7 +98,7 @@ internal class CallStackDistanceCalculatorTests { callStack.push("C", 2) val calculator = - CallStackDistanceCalculator(setOf("C" to 4), ::getCfgDistance, ::getCfgDistanceToExitPoint) + CallStackDistanceCalculator(setOf("C" to 4), cfgStatistics) assertEquals(10u, calculator.calculateDistance(currentStatment, callStack)) calculator.removeTarget("C", 4) diff --git a/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt b/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt index 0a8927dc36..e133776de7 100644 --- a/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt @@ -1,5 +1,6 @@ package org.usvm.statistics +import kotlin.test.assertEquals import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource @@ -7,12 +8,12 @@ import org.usvm.TestInstruction import org.usvm.UCallStack import org.usvm.appGraph import org.usvm.callStackOf -import org.usvm.statistics.distances.CallGraphReachabilityStatistics -import org.usvm.statistics.distances.DistanceStatistics +import org.usvm.statistics.distances.CallGraphStatisticsImpl +import org.usvm.statistics.distances.CfgStatisticsImpl import org.usvm.statistics.distances.InterprocDistance import org.usvm.statistics.distances.InterprocDistanceCalculator +import org.usvm.statistics.distances.PlainCallGraphStatistics import org.usvm.statistics.distances.ReachabilityKind -import kotlin.test.assertEquals class InterprocDistanceCalculatorTests { @@ -169,15 +170,18 @@ class InterprocDistanceCalculatorTests { targetLoc: TestInstruction, expectedDist: InterprocDistance ) { - val distanceStatistics = DistanceStatistics(appGraph1) - val callGraphReachabilityStatistics = CallGraphReachabilityStatistics(callGraphReachabilityDepth.toUInt(), appGraph1) + val cfgStatistics = CfgStatisticsImpl(appGraph1) + val callGraphStatistics = + when (callGraphReachabilityDepth) { + 0 -> PlainCallGraphStatistics() + else -> CallGraphStatisticsImpl(callGraphReachabilityDepth.toUInt(), appGraph1) + } + val calculator = InterprocDistanceCalculator( targetLoc.method to targetLoc, appGraph1, - distanceStatistics::getShortestCfgDistance, - distanceStatistics::getShortestCfgDistanceToExitPoint, - checkReachabilityInCallGraph = - if (callGraphReachabilityDepth == 0) { m1, m2 -> m1 == m2 } else { m1, m2 -> callGraphReachabilityStatistics.checkReachability(m1, m2) } + cfgStatistics, + callGraphStatistics ) assertEquals(expectedDist, calculator.calculateDistance(fromLoc, callStack)) } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt new file mode 100644 index 0000000000..feba5f7d6a --- /dev/null +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt @@ -0,0 +1,57 @@ +package org.usvm.machine + +import org.jacodb.api.JcClassType +import org.jacodb.api.JcMethod +import org.jacodb.api.JcType +import org.jacodb.api.ext.findMethodOrNull +import org.jacodb.api.ext.toType +import org.usvm.statistics.distances.CallGraphStatistics +import org.usvm.types.UTypeStream +import org.usvm.util.limitedBfsTraversal +import java.util.concurrent.ConcurrentHashMap + +/** + * [CallGraphStatistics] Java caching implementation with thread-safe results caching. Overridden methods are considered + * according to [typeStream] and [subclassesToTake]. + * + * @param depthLimit depthLimit methods which are reachable via paths longer than this value are + * not considered (i.e. 1 means that the target method should be directly called from source method). + * @param applicationGraph [JcApplicationGraph] used to get callees info. + * @param typeStream [UTypeStream] used to resolve method overrides. + * @param subclassesToTake only method overrides from [subclassesToTake] first subtypes returned by [typeStream] are + * considered during traversal. If equal to zero, method overrides are not considered during traversal at all. + */ +class JcCallGraphStatistics( + private val depthLimit: UInt, + private val applicationGraph: JcApplicationGraph, + private val typeStream: UTypeStream, + private val subclassesToTake: Int = 0 +) : CallGraphStatistics { + + private val cache = ConcurrentHashMap>() + + private fun getCallees(method: JcMethod): Sequence { + val rawCallees = applicationGraph.statementsOf(method).flatMap(applicationGraph::callees).distinct() + + if (subclassesToTake <= 0 || !rawCallees.any()) { + return rawCallees + } + + // TODO: check that the method was actually overridden or base implementation is used + return typeStream + .filterBySupertype(method.enclosingClass.toType()) + .take(subclassesToTake) + .asSequence() + .map { + checkNotNull((it as? JcClassType)?.findMethodOrNull(method.name, method.description)?.method) { + "Cannot find overridden method $method in type $it" + } + } + .distinct() + } + + override fun checkReachability(methodFrom: JcMethod, methodTo: JcMethod): Boolean = + cache.computeIfAbsent(methodFrom) { + limitedBfsTraversal(depthLimit, listOf(methodFrom), ::getCallees).toSet() + }.contains(methodTo) +} diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt index b7084b96c7..97f0c83ab9 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt @@ -22,7 +22,8 @@ import org.usvm.statistics.TransitiveCoverageZoneObserver import org.usvm.statistics.UMachineObserver import org.usvm.statistics.collectors.CoveredNewStatesCollector import org.usvm.statistics.collectors.TargetsReachedStatesCollector -import org.usvm.statistics.distances.DistanceStatistics +import org.usvm.statistics.distances.CfgStatisticsImpl +import org.usvm.statistics.distances.PlainCallGraphStatistics import org.usvm.stopstrategies.createStopStrategy val logger = object : KLogging() {}.logger @@ -39,7 +40,7 @@ class JcMachine( private val interpreter = JcInterpreter(ctx, applicationGraph) - private val distanceStatistics = DistanceStatistics(applicationGraph) + private val cfgStatistics = CfgStatisticsImpl(applicationGraph) fun analyze(method: JcMethod, targets: List = emptyList()): List { logger.debug("{}.analyze({}, {})", this, method, targets) @@ -60,12 +61,24 @@ class JcMachine( applicationGraph ) + val callGraphStatistics = + when (options.targetSearchDepth) { + 0u -> PlainCallGraphStatistics() + else -> JcCallGraphStatistics( + options.targetSearchDepth, + applicationGraph, + typeSystem.topTypeStream(), + 10 + ) + } + val pathSelector = createPathSelector( initialState, options, applicationGraph, { coverageStatistics }, - { distanceStatistics } + { cfgStatistics }, + { callGraphStatistics } ) val statesCollector = diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt index a7c3e4604a..0c80f79394 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt @@ -16,7 +16,9 @@ import org.usvm.statistics.TerminatedStateRemover import org.usvm.statistics.UMachineObserver import org.usvm.statistics.collectors.CoveredNewStatesCollector import org.usvm.statistics.collectors.TargetsReachedStatesCollector -import org.usvm.statistics.distances.DistanceStatistics +import org.usvm.statistics.distances.CallGraphStatisticsImpl +import org.usvm.statistics.distances.CfgStatisticsImpl +import org.usvm.statistics.distances.PlainCallGraphStatistics import org.usvm.stopstrategies.createStopStrategy /** @@ -35,19 +37,29 @@ class SampleMachine( private val interpreter = SampleInterpreter(ctx, applicationGraph) private val resultModelConverter = ResultModelConverter(ctx) - private val distanceStatistics = DistanceStatistics(applicationGraph) + private val cfgStatistics = CfgStatisticsImpl(applicationGraph) fun analyze(method: Method<*>, targets: List = emptyList()): Collection { val initialState = getInitialState(method, targets) val coverageStatistics: CoverageStatistics, Stmt, SampleState> = CoverageStatistics(setOf(method), applicationGraph) + val callGraphStatistics = + when (options.targetSearchDepth) { + 0u -> PlainCallGraphStatistics() + else -> CallGraphStatisticsImpl( + options.targetSearchDepth, + applicationGraph + ) + } + val pathSelector = createPathSelector( initialState, options, applicationGraph, { coverageStatistics }, - { distanceStatistics } + { cfgStatistics }, + { callGraphStatistics } ) val statesCollector = From c6aaa86a163faa806bf272a95cb451e55f696001 Mon Sep 17 00:00:00 2001 From: Maksim Parshin Date: Wed, 6 Sep 2023 14:56:58 +0300 Subject: [PATCH 08/17] Remove specific targets implementations --- usvm-core/src/main/kotlin/org/usvm/Targets.kt | 14 +------ .../kotlin/org/usvm/ps/PathSelectorFactory.kt | 17 ++++---- .../distances/CallStackDistanceCalculator.kt | 2 +- .../distances/DistanceCalculator.kt | 10 ++--- .../distances/InterprocDistanceCalculator.kt | 2 +- .../org/usvm/api/targets/JcExitTarget.kt | 14 ------- .../org/usvm/api/targets/JcLocationTarget.kt | 23 ----------- .../targets/JcNullPointerDereferenceTarget.kt | 19 --------- .../kotlin/org/usvm/api/targets/JcTarget.kt | 9 +--- .../main/kotlin/org/usvm/machine/JcContext.kt | 2 - .../main/kotlin/org/usvm/machine/JcMachine.kt | 21 ++++------ .../machine/interpreter/JcExprResolver.kt | 5 +-- .../interpreter/JcInterpreterObserver.kt | 33 --------------- .../targets/NullPointerDereference.java | 20 --------- .../targets/TestNullPointerDereference.kt | 41 ------------------- .../kotlin/org/usvm/machine/SampleMachine.kt | 1 - 16 files changed, 27 insertions(+), 206 deletions(-) delete mode 100644 usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcExitTarget.kt delete mode 100644 usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt delete mode 100644 usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt delete mode 100644 usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreterObserver.kt delete mode 100644 usvm-jvm/src/samples/java/org/usvm/samples/targets/NullPointerDereference.java delete mode 100644 usvm-jvm/src/test/kotlin/org/usvm/samples/targets/TestNullPointerDereference.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/Targets.kt b/usvm-core/src/main/kotlin/org/usvm/Targets.kt index fa1b46aed4..5c761e2a6b 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Targets.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Targets.kt @@ -19,7 +19,7 @@ abstract class UTarget? = null -) : UMachineObserver { +) { private val childrenImpl = mutableListOf() private var parent: Target? = null @@ -54,10 +54,6 @@ abstract class UTarget Unit) { - children.forEach { if (!it.isRemoved) it.action() } - } - /** * This method should be called by concrete targets to signal that [byState] * should try to visit the target. If the target without children has been @@ -83,12 +79,4 @@ abstract class UTarget) { - forEachChild { onState(parent, forks) } - } - - override fun onStateTerminated(state: State) { - forEachChild { onStateTerminated(state) } - } } diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt index b1977b2b2e..9c2de07a66 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt @@ -13,7 +13,7 @@ import org.usvm.statistics.CoverageStatistics import org.usvm.statistics.distances.CallGraphStatistics import org.usvm.statistics.distances.CallStackDistanceCalculator import org.usvm.statistics.distances.CfgStatistics -import org.usvm.statistics.distances.DynamicTargetsShortestDistanceCalculator +import org.usvm.statistics.distances.MultiTargetDistanceCalculator import org.usvm.statistics.distances.InterprocDistance import org.usvm.statistics.distances.InterprocDistanceCalculator import org.usvm.statistics.distances.ReachabilityKind @@ -189,7 +189,7 @@ internal fun , random: Random? = null, ): UPathSelector { - val distanceCalculator = DynamicTargetsShortestDistanceCalculator { m, s -> + val distanceCalculator = MultiTargetDistanceCalculator { m, s -> CallStackDistanceCalculator( targets = listOf(m to s), cfgStatistics = cfgStatistics @@ -199,7 +199,7 @@ internal fun if (target.location == null) { - 0u // i. e. ExitTarget case + 0u } else { distanceCalculator.calculateDistance( state.currentStatement, @@ -222,13 +222,16 @@ internal fun , State : UState<*, Method, Statement, *, Target, State>> createTargetedPathSelector( @@ -237,7 +240,7 @@ internal fun , random: Random? = null, ): UPathSelector { - val distanceCalculator = DynamicTargetsShortestDistanceCalculator { m, s -> + val distanceCalculator = MultiTargetDistanceCalculator { m, s -> InterprocDistanceCalculator( targetLocation = m to s, applicationGraph = applicationGraph, @@ -249,7 +252,7 @@ internal fun if (target.location == null) { - 0u // i. e. ExitTarget case + 0u } else { distanceCalculator.calculateDistance( state.currentStatement, diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt index 8cece0dbbc..2bbbe82f6d 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt @@ -16,7 +16,7 @@ import org.usvm.UCallStack class CallStackDistanceCalculator( targets: Collection>, private val cfgStatistics: CfgStatistics -) : StaticTargetsDistanceCalculator { +) : DistanceCalculator { // TODO: optimize for single target case private val targetsByMethod = HashMap>() diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt index 54890b5d40..9e6f287675 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt @@ -5,7 +5,7 @@ import org.usvm.UCallStack /** * @see calculateDistance */ -fun interface StaticTargetsDistanceCalculator { +fun interface DistanceCalculator { /** * Calculate distance from location represented by [currentStatement] and [callStack] to @@ -15,13 +15,13 @@ fun interface StaticTargetsDistanceCalculator { } /** - * Dynamically accumulates multiple [StaticTargetsDistanceCalculator] by their targets allowing + * Dynamically accumulates multiple [DistanceCalculator] by their targets allowing * to calculate distances to arbitrary targets. */ -class DynamicTargetsShortestDistanceCalculator( - private val getDistanceCalculator: (Method, Statement) -> StaticTargetsDistanceCalculator +class MultiTargetDistanceCalculator( + private val getDistanceCalculator: (Method, Statement) -> DistanceCalculator ) { - private val calculatorsByTarget = HashMap, StaticTargetsDistanceCalculator>() + private val calculatorsByTarget = HashMap, DistanceCalculator>() // TODO: use fun removeTargetFromCache(target: Pair): Boolean { diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt index 22fe839257..dd1daa34a8 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt @@ -47,7 +47,7 @@ internal class InterprocDistanceCalculator( private val applicationGraph: ApplicationGraph, private val cfgStatistics: CfgStatistics, private val callGraphStatistics: CallGraphStatistics -) : StaticTargetsDistanceCalculator { +) : DistanceCalculator { private val frameDistanceCache = HashMap>() diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcExitTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcExitTarget.kt deleted file mode 100644 index 545c99d4b6..0000000000 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcExitTarget.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.usvm.api.targets - -import org.usvm.machine.state.JcState - -/** - * [JcTarget] which is reached when a state is terminated. Can be used to - * force termination of the states, which have visited [JcExitTarget]'s parent targets. - */ -class JcExitTarget : JcTarget() { - override fun onStateTerminated(state: JcState) { - visit(state) - super.onStateTerminated(state) - } -} diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt deleted file mode 100644 index 0a28b025b5..0000000000 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcLocationTarget.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.usvm.api.targets - -import org.jacodb.api.JcMethod -import org.jacodb.api.cfg.JcInst -import org.usvm.machine.state.JcState - -/** - * [JcTarget] which is reached when location ([method], [inst]) is reached. - */ -class JcLocationTarget(private val method: JcMethod, private val inst: JcInst) : JcTarget(method to inst) { - - private fun hasReached(state: JcState): Boolean { - return state.callStack.isNotEmpty() && method == state.callStack.lastMethod() && inst == state.currentStatement - } - - override fun onState(parent: JcState, forks: Sequence) { - val reachedState = if (hasReached(parent)) parent else forks.find(::hasReached) - if (reachedState != null) { - visit(reachedState) - } - super.onState(parent, forks) - } -} diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt deleted file mode 100644 index 9119256ca7..0000000000 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcNullPointerDereferenceTarget.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.usvm.api.targets - -import org.jacodb.api.JcMethod -import org.jacodb.api.cfg.JcInst -import org.usvm.UHeapRef -import org.usvm.machine.state.JcState - -/** - * [JcTarget] which is reached when null pointer is dereferenced in location ([method], [inst]). - */ -class JcNullPointerDereferenceTarget(private val method: JcMethod, private val inst: JcInst) : JcTarget(method to inst) { - - override fun onNullPointerDereference(state: JcState, ref: UHeapRef) { - if (state.callStack.lastMethod() == method && state.currentStatement == inst) { - visit(state) - } - super.onNullPointerDereference(state, ref) - } -} diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt index 44e41412cf..7441ac10b4 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt @@ -2,18 +2,11 @@ package org.usvm.api.targets import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst -import org.usvm.UHeapRef import org.usvm.UTarget -import org.usvm.machine.interpreter.JcInterpreterObserver import org.usvm.machine.state.JcState /** * Base class for JcMachine targets. */ abstract class JcTarget(location: Pair? = null -) : UTarget(location), JcInterpreterObserver { - - override fun onNullPointerDereference(state: JcState, ref: UHeapRef) { - forEachChild { onNullPointerDereference(state, ref) } - } -} +) : UTarget(location) diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcContext.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcContext.kt index d3e7a2bcc8..a35140c3b0 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcContext.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcContext.kt @@ -29,8 +29,6 @@ class JcContext( val cp: JcClasspath, components: JcComponents, ) : UContext(components) { - val jcInterpreterObservers = CompositeJcInterpreterObserver() - val voidSort by lazy { JcVoidSort(this) } val longSort get() = bv64Sort diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt index 97f0c83ab9..405bf83812 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt @@ -98,7 +98,6 @@ class JcMachine( val observers = mutableListOf>(coverageStatistics) observers.add(TerminatedStateRemover()) - observers.addAll(targets) if (options.coverageZone == CoverageZone.TRANSITIVE) { observers.add( @@ -112,19 +111,13 @@ class JcMachine( } observers.add(statesCollector) - targets.forEach { ctx.jcInterpreterObservers += it } - try { - run( - interpreter, - pathSelector, - observer = CompositeUMachineObserver(observers), - isStateTerminated = ::isStateTerminated, - stopStrategy = stopStrategy, - ) - } finally { - targets.forEach { ctx.jcInterpreterObservers -= it } - } - + run( + interpreter, + pathSelector, + observer = CompositeUMachineObserver(observers), + isStateTerminated = ::isStateTerminated, + stopStrategy = stopStrategy, + ) return statesCollector.collectedStates } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt index cbbd7dbdd3..76c649e1db 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt @@ -689,10 +689,7 @@ class JcExprResolver( val neqNull = mkHeapRefEq(ref, nullRef).not() scope.fork( neqNull, - blockOnFalseState = { - ctx.jcInterpreterObservers.onNullPointerDereference(this, ref) - allocateException(nullPointerExceptionType)(this) - } + blockOnFalseState = allocateException(nullPointerExceptionType) ) } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreterObserver.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreterObserver.kt deleted file mode 100644 index c3fb485a3e..0000000000 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreterObserver.kt +++ /dev/null @@ -1,33 +0,0 @@ -package org.usvm.machine.interpreter - -import org.usvm.UHeapRef -import org.usvm.machine.state.JcState - -/** - * JcInterpreter events observer. - */ -interface JcInterpreterObserver { - - /** - * Called when [state] dereferences null pointer in [ref]. - */ - fun onNullPointerDereference(state: JcState, ref: UHeapRef) { } -} - -class CompositeJcInterpreterObserver(observers: List) : JcInterpreterObserver { - private val observers = observers.toMutableList() - - constructor(vararg observers: JcInterpreterObserver) : this(observers.toMutableList()) - - override fun onNullPointerDereference(state: JcState, ref: UHeapRef) { - observers.forEach { it.onNullPointerDereference(state, ref) } - } - - operator fun plusAssign(observer: JcInterpreterObserver) { - observers.add(observer) - } - - operator fun minusAssign(observer: JcInterpreterObserver) { - observers.remove(observer) - } -} diff --git a/usvm-jvm/src/samples/java/org/usvm/samples/targets/NullPointerDereference.java b/usvm-jvm/src/samples/java/org/usvm/samples/targets/NullPointerDereference.java deleted file mode 100644 index aee8e776ed..0000000000 --- a/usvm-jvm/src/samples/java/org/usvm/samples/targets/NullPointerDereference.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.usvm.samples.targets; - -class ClassWithValue { - public int value; -} - -public class NullPointerDereference { - - public static int twoPathsToNPE(ClassWithValue obj, int n) { - int m = 0; - - if (n > 100) { - m += n; - } else { - m -= n; - } - - return obj.value + m; - } -} diff --git a/usvm-jvm/src/test/kotlin/org/usvm/samples/targets/TestNullPointerDereference.kt b/usvm-jvm/src/test/kotlin/org/usvm/samples/targets/TestNullPointerDereference.kt deleted file mode 100644 index f1326dc18c..0000000000 --- a/usvm-jvm/src/test/kotlin/org/usvm/samples/targets/TestNullPointerDereference.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.usvm.samples.targets - -import org.junit.jupiter.api.Test -import org.usvm.PathSelectionStrategy -import org.usvm.UMachineOptions -import org.usvm.api.targets.JcExitTarget -import org.usvm.api.targets.JcLocationTarget -import org.usvm.api.targets.JcNullPointerDereferenceTarget -import org.usvm.samples.JavaMethodTestRunner -import org.usvm.test.util.checkers.eq -import org.usvm.util.getJcMethod - -class TestNullPointerDereference : JavaMethodTestRunner() { - - @Test - fun `Null Pointer Dereference with intermediate target`() { - val jcMethod = cp.getJcMethod(NullPointerDereference::twoPathsToNPE) - - val target = JcLocationTarget(jcMethod, jcMethod.instList[5]).apply { - addChild(JcNullPointerDereferenceTarget(jcMethod, jcMethod.instList[8])).apply { - addChild(JcExitTarget()) - } - } - - withOptions( - UMachineOptions( - pathSelectionStrategies = listOf(PathSelectionStrategy.TARGETED), - stopOnTargetsReached = true - ) - ) { - withTargets(listOf(target)) { - // In fact, checking that the first state which was reported has reached the target - checkDiscoveredPropertiesWithExceptions( - NullPointerDereference::twoPathsToNPE, - eq(1), - { _, n, r -> n <= 100 && r.exceptionOrNull() is NullPointerException } - ) - } - } - } -} diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt index 0c80f79394..86a69913a9 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt @@ -79,7 +79,6 @@ class SampleMachine( val observers = mutableListOf>(coverageStatistics) observers.add(TerminatedStateRemover()) - observers.addAll(targets) observers.add(statesCollector) run( From 1ff25de2e6fd8a519d1e45b6e6e0569aa199cf6c Mon Sep 17 00:00:00 2001 From: Maksim Parshin Date: Wed, 6 Sep 2023 17:58:20 +0300 Subject: [PATCH 09/17] PR comments fixes --- .../src/main/kotlin/org/usvm/CallStack.kt | 2 +- .../src/main/kotlin/org/usvm/Location.kt | 3 + usvm-core/src/main/kotlin/org/usvm/State.kt | 14 ++--- usvm-core/src/main/kotlin/org/usvm/Targets.kt | 14 ++--- .../kotlin/org/usvm/ps/PathSelectorFactory.kt | 26 ++++++--- .../org/usvm/statistics/CoverageStatistics.kt | 5 +- .../TargetsReachedStatesCollector.kt | 4 +- .../distances/CallGraphStatisticsImpl.kt | 7 ++- .../distances/CallStackDistanceCalculator.kt | 5 +- .../distances/DistanceCalculator.kt | 11 ++-- .../distances/InterprocDistanceCalculator.kt | 56 +++++++++++++------ .../TargetsReachedStopStrategy.kt | 2 +- .../src/test/kotlin/org/usvm/TestUtil.kt | 6 +- .../CallStackDistanceCalculatorTests.kt | 11 +++- .../InterprocDistanceCalculatorTests.kt | 3 +- .../kotlin/org/usvm/api/targets/JcTarget.kt | 4 +- .../org/usvm/machine/JcCallGraphStatistics.kt | 16 +++--- .../kotlin/org/usvm/machine/SampleTarget.kt | 3 +- .../main/kotlin/org/usvm/UMachineOptions.kt | 4 +- .../kotlin/org/usvm/algorithms/GraphUtils.kt | 29 +++++----- .../usvm/test/GraphUtilsLimitedBfsTests.kt | 34 +++++++++++ 21 files changed, 169 insertions(+), 90 deletions(-) create mode 100644 usvm-core/src/main/kotlin/org/usvm/Location.kt create mode 100644 usvm-util/src/test/kotlin/org/usvm/test/GraphUtilsLimitedBfsTests.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/CallStack.kt b/usvm-core/src/main/kotlin/org/usvm/CallStack.kt index ca615309ce..b8fcb2ad54 100644 --- a/usvm-core/src/main/kotlin/org/usvm/CallStack.kt +++ b/usvm-core/src/main/kotlin/org/usvm/CallStack.kt @@ -12,7 +12,7 @@ data class UStackTraceFrame( class UCallStack private constructor( private val stack: ArrayDeque>, -) : Collection> by stack { +) : List> by stack { constructor() : this(ArrayDeque()) constructor(method: Method) : this( ArrayDeque>().apply { diff --git a/usvm-core/src/main/kotlin/org/usvm/Location.kt b/usvm-core/src/main/kotlin/org/usvm/Location.kt new file mode 100644 index 0000000000..f2e8ccc9dc --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/Location.kt @@ -0,0 +1,3 @@ +package org.usvm + +data class Location(val method: Method, val statement: Statement) diff --git a/usvm-core/src/main/kotlin/org/usvm/State.kt b/usvm-core/src/main/kotlin/org/usvm/State.kt index c1a0514e33..8a86b7c57b 100644 --- a/usvm-core/src/main/kotlin/org/usvm/State.kt +++ b/usvm-core/src/main/kotlin/org/usvm/State.kt @@ -79,7 +79,7 @@ abstract class UState() + private val reachedTerminalTargetsImpl = mutableSetOf() /** * Collection of state's current targets. @@ -90,22 +90,22 @@ abstract class UState = reachedSinksImpl + val reachedTerminalTargets: Set = reachedTerminalTargetsImpl /** - * Tries to remove the [target] from current targets collection and - * add its children there. + * If the [target] is not removed and is contained in this state's target collection, + * removes it from there and adds there all its children. * * @return true if the [target] was successfully removed. */ - internal fun visitTarget(target: Target): Boolean { + internal fun tryPropagateTarget(target: Target): Boolean { val previousTargetCount = targetsImpl.size targetsImpl = targetsImpl.remove(target) if (previousTargetCount == targetsImpl.size || !target.isRemoved) { return false } - if (target.isSink) { - reachedSinksImpl.add(target) + if (target.isTerminal) { + reachedTerminalTargetsImpl.add(target) return true } targetsImpl = targetsImpl.addAll(target.children) diff --git a/usvm-core/src/main/kotlin/org/usvm/Targets.kt b/usvm-core/src/main/kotlin/org/usvm/Targets.kt index 5c761e2a6b..27f4bf676a 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Targets.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Targets.kt @@ -1,7 +1,5 @@ package org.usvm -import org.usvm.statistics.UMachineObserver - /** * Base class for a symbolic execution target. A target can be understood as a 'task' for symbolic machine * which it tries to complete. For example, a task can have an attached location which should be visited by a state @@ -18,7 +16,7 @@ abstract class UTarget? = null + val location: Location? = null ) { private val childrenImpl = mutableListOf() private var parent: Target? = null @@ -31,7 +29,7 @@ abstract class UTarget, random: Random? = null, ): UPathSelector { - val distanceCalculator = MultiTargetDistanceCalculator { m, s -> + val distanceCalculator = MultiTargetDistanceCalculator { loc -> CallStackDistanceCalculator( - targets = listOf(m to s), + targets = listOf(loc), cfgStatistics = cfgStatistics ) } @@ -223,13 +224,20 @@ internal fun , random: Random? = null, ): UPathSelector { - val distanceCalculator = MultiTargetDistanceCalculator { m, s -> + val distanceCalculator = MultiTargetDistanceCalculator { loc -> InterprocDistanceCalculator( - targetLocation = m to s, + targetLocation = loc, applicationGraph = applicationGraph, cfgStatistics = cfgStatistics, callGraphStatistics = callGraphStatistics diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt index 443cc1bd98..5821d068ff 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt @@ -1,5 +1,6 @@ package org.usvm.statistics +import org.usvm.Location import org.usvm.UState import org.usvm.algorithms.bfsTraversal import java.util.concurrent.ConcurrentHashMap @@ -85,8 +86,8 @@ class CoverageStatistics> { - return uncoveredStatements.flatMap { kvp -> kvp.value.map { kvp.key to it } } + fun getUncoveredStatements(): Collection> { + return uncoveredStatements.flatMap { kvp -> kvp.value.map { Location(kvp.key, it) } } } /** diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt index e8f1b4a904..9913c2fbe2 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt @@ -4,14 +4,14 @@ import org.usvm.UState /** * [StatesCollector] implementation collecting only those states which have reached - * any sink targets. + * any terminal targets. */ class TargetsReachedStatesCollector> : StatesCollector { private val mutableCollectedStates = mutableListOf() override val collectedStates: List = mutableCollectedStates override fun onStateTerminated(state: State) { - if (state.reachedSinks.isNotEmpty()) { + if (state.reachedTerminalTargets.isNotEmpty()) { mutableCollectedStates.add(state) } } diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatisticsImpl.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatisticsImpl.kt index 34bd465f51..342186350d 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatisticsImpl.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatisticsImpl.kt @@ -19,11 +19,12 @@ class CallGraphStatisticsImpl( private val cache = ConcurrentHashMap>() - private fun getCallees(method: Method): Sequence = - applicationGraph.statementsOf(method).flatMap(applicationGraph::callees).distinct() + private fun getCallees(method: Method): Set = + applicationGraph.statementsOf(method).flatMapTo(mutableSetOf(), applicationGraph::callees) override fun checkReachability(methodFrom: Method, methodTo: Method): Boolean = cache.computeIfAbsent(methodFrom) { - limitedBfsTraversal(depthLimit, listOf(methodFrom), ::getCallees).toSet() + // TODO: stop traversal on reaching methodTo and cache remaining elements + limitedBfsTraversal(depthLimit, listOf(methodFrom), ::getCallees) }.contains(methodTo) } diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt index 2bbbe82f6d..46b86acb75 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt @@ -1,5 +1,6 @@ package org.usvm.statistics.distances +import org.usvm.Location import kotlin.math.min import org.usvm.UCallStack @@ -14,7 +15,7 @@ import org.usvm.UCallStack * @param cfgStatistics [CfgStatistics] instance used to calculate local distances on each frame. */ class CallStackDistanceCalculator( - targets: Collection>, + targets: Collection>, private val cfgStatistics: CfgStatistics ) : DistanceCalculator { @@ -30,7 +31,7 @@ class CallStackDistanceCalculator( } private fun getMinDistanceToTargetInCurrentFrame(method: Method, statement: Statement): UInt { - return minLocalDistanceToTargetCache.computeIfAbsent(method) { HashMap() } + return minLocalDistanceToTargetCache.computeIfAbsent(method) { hashMapOf() } .computeIfAbsent(statement) { targetsByMethod[method]?.minOfOrNull { cfgStatistics.getShortestDistance(method, statement, it) } ?: UInt.MAX_VALUE } diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt index 9e6f287675..b39160c9e1 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt @@ -1,5 +1,6 @@ package org.usvm.statistics.distances +import org.usvm.Location import org.usvm.UCallStack /** @@ -19,12 +20,12 @@ fun interface DistanceCalculator { * to calculate distances to arbitrary targets. */ class MultiTargetDistanceCalculator( - private val getDistanceCalculator: (Method, Statement) -> DistanceCalculator + private val getDistanceCalculator: (Location) -> DistanceCalculator ) { - private val calculatorsByTarget = HashMap, DistanceCalculator>() + private val calculatorsByTarget = HashMap, DistanceCalculator>() // TODO: use - fun removeTargetFromCache(target: Pair): Boolean { + fun removeTargetFromCache(target: Location): Boolean { return calculatorsByTarget.remove(target) != null } @@ -34,9 +35,9 @@ class MultiTargetDistanceCalculator( fun calculateDistance( currentStatement: Statement, callStack: UCallStack, - target: Pair + target: Location ): Distance { - val calculator = calculatorsByTarget.computeIfAbsent(target) { getDistanceCalculator(it.first, it.second) } + val calculator = calculatorsByTarget.computeIfAbsent(target) { getDistanceCalculator(it) } return calculator.calculateDistance(currentStatement, callStack) } } diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt index dd1daa34a8..b18e14bf0e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt @@ -1,5 +1,6 @@ package org.usvm.statistics.distances +import org.usvm.Location import org.usvm.UCallStack import org.usvm.statistics.ApplicationGraph @@ -11,7 +12,6 @@ enum class ReachabilityKind { * Target is located in the same method and is locally reachable. */ LOCAL, - /** * Target is reachable from some method which can be called later. */ @@ -26,8 +26,19 @@ enum class ReachabilityKind { NONE } -data class InterprocDistance(val distance: UInt, val reachabilityKind: ReachabilityKind) { - val isUnreachable = reachabilityKind == ReachabilityKind.NONE +class InterprocDistance(val distance: UInt, reachabilityKind: ReachabilityKind) { + + val isInfinite = distance == UInt.MAX_VALUE + val reachabilityKind = if (distance != UInt.MAX_VALUE) reachabilityKind else ReachabilityKind.NONE + + override fun equals(other: Any?): Boolean { + if (other !is InterprocDistance) { + return false + } + return other.distance == distance && other.reachabilityKind == reachabilityKind + } + + override fun hashCode(): Int = (23 * 31 + distance.toInt()) * 31 + reachabilityKind.hashCode() } /** @@ -43,7 +54,7 @@ data class InterprocDistance(val distance: UInt, val reachabilityKind: Reachabil // TODO: give priority to paths without calls // TODO: add new targets according to the path? internal class InterprocDistanceCalculator( - private val targetLocation: Pair, + private val targetLocation: Location, private val applicationGraph: ApplicationGraph, private val cfgStatistics: CfgStatistics, private val callGraphStatistics: CallGraphStatistics @@ -51,17 +62,17 @@ internal class InterprocDistanceCalculator( private val frameDistanceCache = HashMap>() - private fun calculateFrameDistance(method: Method, statement: Statement): Pair { - if (method == targetLocation.first) { - val localDistance = cfgStatistics.getShortestDistance(method, statement, targetLocation.second) + private fun calculateFrameDistance(method: Method, statement: Statement): InterprocDistance { + if (method == targetLocation.method) { + val localDistance = cfgStatistics.getShortestDistance(method, statement, targetLocation.statement) if (localDistance != UInt.MAX_VALUE) { - return localDistance to true + return InterprocDistance(localDistance, ReachabilityKind.LOCAL) } } val cached = frameDistanceCache[method]?.get(statement) if (cached != null) { - return cached to false + return InterprocDistance(cached, ReachabilityKind.UP_STACK) } var minDistanceToCall = UInt.MAX_VALUE @@ -75,15 +86,17 @@ internal class InterprocDistanceCalculator( continue } - if (applicationGraph.callees(statementOfMethod).any { callGraphStatistics.checkReachability(it, targetLocation.first) }) { + if (applicationGraph.callees(statementOfMethod).any { + callGraphStatistics.checkReachability(it, targetLocation.method) + }) { minDistanceToCall = distanceToCall } } if (minDistanceToCall != UInt.MAX_VALUE) { - frameDistanceCache.computeIfAbsent(method) { HashMap() }[statement] = minDistanceToCall + frameDistanceCache.computeIfAbsent(method) { hashMapOf() }[statement] = minDistanceToCall } - return minDistanceToCall to false + return InterprocDistance(minDistanceToCall, ReachabilityKind.UP_STACK) } override fun calculateDistance( @@ -91,17 +104,24 @@ internal class InterprocDistanceCalculator( callStack: UCallStack ): InterprocDistance { val lastMethod = callStack.lastMethod() - val (lastFrameDistance, isLocal) = calculateFrameDistance(lastMethod, currentStatement) - if (lastFrameDistance != UInt.MAX_VALUE) { - return InterprocDistance(lastFrameDistance, if (isLocal) ReachabilityKind.LOCAL else ReachabilityKind.UP_STACK) + val lastFrameDistance = calculateFrameDistance(lastMethod, currentStatement) + if (!lastFrameDistance.isInfinite) { + return lastFrameDistance } + listOf().asReversed() + var statementOnCallStack = callStack.last().returnSite - for ((methodOnCallStack, returnSite) in callStack.reversed().drop(1)) { + for (i in callStack.size - 2 downTo 0) { + val (methodOnCallStack, returnSite) = callStack[i] checkNotNull(statementOnCallStack) { "Not first call stack frame had null return site" } - if (applicationGraph.successors(statementOnCallStack).any { calculateFrameDistance(methodOnCallStack, it).first != UInt.MAX_VALUE }) { - return InterprocDistance(cfgStatistics.getShortestDistanceToExit(lastMethod, currentStatement), ReachabilityKind.DOWN_STACK) + fun isTargetReachableFrom(statement: Statement): Boolean = + !calculateFrameDistance(methodOnCallStack, statement).isInfinite + + if (applicationGraph.successors(statementOnCallStack).any(::isTargetReachableFrom)) { + val distanceToExit = cfgStatistics.getShortestDistanceToExit(lastMethod, currentStatement) + return InterprocDistance(distanceToExit, ReachabilityKind.DOWN_STACK) } statementOnCallStack = returnSite diff --git a/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt index 11d7f750ff..ceca4c33ff 100644 --- a/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt +++ b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt @@ -3,7 +3,7 @@ package org.usvm.stopstrategies import org.usvm.UTarget /** - * A stop strategy which stops when all sinks in [targets] are reached. + * A stop strategy which stops when all terminal targets in [targets] are reached. */ class TargetsReachedStopStrategy(private val targets: Collection>) : StopStrategy { override fun shouldStop(): Boolean = targets.all { it.isRemoved } diff --git a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt index e45125b72c..9e25236b58 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt @@ -29,9 +29,11 @@ internal fun pseudoRandom(i: Int): Int { return res } -internal class TestTarget(method: String, offset: Int) : UTarget(method to TestInstruction(method, offset)) { +internal class TestTarget(method: String, offset: Int) : UTarget( + Location(method, TestInstruction(method, offset)) +) { fun reach(state: TestState) { - visit(state) + propagate(state) } } diff --git a/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt b/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt index d34eaab0af..f10c43cc95 100644 --- a/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt @@ -1,6 +1,7 @@ package org.usvm.statistics import org.junit.jupiter.api.Test +import org.usvm.Location import org.usvm.UCallStack import org.usvm.statistics.distances.CallStackDistanceCalculator import org.usvm.statistics.distances.CfgStatistics @@ -60,7 +61,13 @@ internal class CallStackDistanceCalculatorTests { } val calculator = CallStackDistanceCalculator( - setOf("A" to 2, "A" to 3, "A" to 4, "A" to 5, "A" to 6), + setOf( + Location("A", 2), + Location("A", 3), + Location("A", 4), + Location("A", 5), + Location("A", 6) + ), cfgStatistics ) @@ -98,7 +105,7 @@ internal class CallStackDistanceCalculatorTests { callStack.push("C", 2) val calculator = - CallStackDistanceCalculator(setOf("C" to 4), cfgStatistics) + CallStackDistanceCalculator(setOf(Location("C", 4)), cfgStatistics) assertEquals(10u, calculator.calculateDistance(currentStatment, callStack)) calculator.removeTarget("C", 4) diff --git a/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt b/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt index e133776de7..ce644b8fee 100644 --- a/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt @@ -4,6 +4,7 @@ import kotlin.test.assertEquals import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource +import org.usvm.Location import org.usvm.TestInstruction import org.usvm.UCallStack import org.usvm.appGraph @@ -178,7 +179,7 @@ class InterprocDistanceCalculatorTests { } val calculator = InterprocDistanceCalculator( - targetLoc.method to targetLoc, + Location(targetLoc.method, targetLoc), appGraph1, cfgStatistics, callGraphStatistics diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt index 7441ac10b4..cef6959d61 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt @@ -2,11 +2,13 @@ package org.usvm.api.targets import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst +import org.usvm.Location import org.usvm.UTarget import org.usvm.machine.state.JcState /** * Base class for JcMachine targets. */ -abstract class JcTarget(location: Pair? = null +abstract class JcTarget( + location: Location? = null ) : UTarget(location) diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt index feba5f7d6a..d0dfd09439 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt @@ -30,28 +30,28 @@ class JcCallGraphStatistics( private val cache = ConcurrentHashMap>() - private fun getCallees(method: JcMethod): Sequence { - val rawCallees = applicationGraph.statementsOf(method).flatMap(applicationGraph::callees).distinct() + private fun getCallees(method: JcMethod): Set { + val callees = mutableSetOf() + applicationGraph.statementsOf(method).flatMapTo(callees, applicationGraph::callees) - if (subclassesToTake <= 0 || !rawCallees.any()) { - return rawCallees + if (subclassesToTake <= 0 || callees.isEmpty()) { + return callees } // TODO: check that the method was actually overridden or base implementation is used return typeStream .filterBySupertype(method.enclosingClass.toType()) .take(subclassesToTake) - .asSequence() - .map { + .mapTo(callees) { checkNotNull((it as? JcClassType)?.findMethodOrNull(method.name, method.description)?.method) { "Cannot find overridden method $method in type $it" } } - .distinct() } override fun checkReachability(methodFrom: JcMethod, methodTo: JcMethod): Boolean = cache.computeIfAbsent(methodFrom) { - limitedBfsTraversal(depthLimit, listOf(methodFrom), ::getCallees).toSet() + // TODO: stop traversal on reaching methodTo and cache remaining elements + limitedBfsTraversal(depthLimit, listOf(methodFrom), ::getCallees) }.contains(methodTo) } diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt index 2db8b01b80..c178943c3d 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt @@ -1,5 +1,6 @@ package org.usvm.machine +import org.usvm.Location import org.usvm.UTarget import org.usvm.language.Method import org.usvm.language.Stmt @@ -7,4 +8,4 @@ import org.usvm.language.Stmt /** * Base class for SampleMachine targets. */ -abstract class SampleTarget(method: Method<*>, statement: Stmt) : UTarget, Stmt, SampleTarget, SampleState>(method to statement) +abstract class SampleTarget(location: Location, Stmt>) : UTarget, Stmt, SampleTarget, SampleState>(location) diff --git a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt index a0c31ad983..797a631e09 100644 --- a/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt +++ b/usvm-util/src/main/kotlin/org/usvm/UMachineOptions.kt @@ -114,7 +114,7 @@ enum class StateCollectionStrategy { */ COVERED_NEW, /** - * Collect only those states which have reached sink targets. + * Collect only those states which have reached terminal targets. */ REACHED_TARGET } @@ -178,7 +178,7 @@ data class UMachineOptions( */ val solverType: SolverType = SolverType.Z3, /** - * Should machine stop when all sink targets are reached. + * Should machine stop when all terminal targets are reached. */ val stopOnTargetsReached: Boolean = false, /** diff --git a/usvm-util/src/main/kotlin/org/usvm/algorithms/GraphUtils.kt b/usvm-util/src/main/kotlin/org/usvm/algorithms/GraphUtils.kt index 493f31e760..711bde822f 100644 --- a/usvm-util/src/main/kotlin/org/usvm/algorithms/GraphUtils.kt +++ b/usvm-util/src/main/kotlin/org/usvm/algorithms/GraphUtils.kt @@ -92,14 +92,14 @@ inline fun bfsTraversal(startVertices: Collection, crossinline adjacentVe } /** - * Returns the sequence of vertices with depth <= [depthLimit] in breadth-first order. + * Returns the set of vertices with depth <= [depthLimit] in breadth-first order. * * @param depthLimit vertices which are reachable via paths longer than this value are * not considered (i.e. 1 means only the vertices adjacent to start). * @param startVertices vertices to start traversal from. - * @param adjacentVertices function which maps a vertex to the sequence of vertices adjacent to. + * @param adjacentVertices function which maps a vertex to the set of vertices adjacent to. */ -fun limitedBfsTraversal(depthLimit: UInt, startVertices: Collection, adjacentVertices: (V) -> Sequence): Sequence { +inline fun limitedBfsTraversal(depthLimit: UInt, startVertices: Collection, crossinline adjacentVertices: (V) -> Set): Set { var currentDepth = 0u var numberOfVerticesOfCurrentLevel = startVertices.size var numberOfVerticesOfNextLevel = 0 @@ -107,20 +107,21 @@ fun limitedBfsTraversal(depthLimit: UInt, startVertices: Collection, adja val queue: Queue = LinkedList(startVertices) val visited = HashSet() - return sequence { - while (currentDepth <= depthLimit && queue.isNotEmpty()) { - val currentVertex = queue.remove() - visited.add(currentVertex) - yield(currentVertex) - adjacentVertices(currentVertex).filterNot(visited::contains).forEach { + while (currentDepth <= depthLimit && queue.isNotEmpty()) { + val currentVertex = queue.remove() + visited.add(currentVertex) + adjacentVertices(currentVertex).forEach { + if (!visited.contains(it)) { numberOfVerticesOfNextLevel++ queue.add(it) } - if (--numberOfVerticesOfCurrentLevel == 0) { - currentDepth++ - numberOfVerticesOfCurrentLevel = numberOfVerticesOfNextLevel - numberOfVerticesOfNextLevel = 0 - } + } + if (--numberOfVerticesOfCurrentLevel == 0) { + currentDepth++ + numberOfVerticesOfCurrentLevel = numberOfVerticesOfNextLevel + numberOfVerticesOfNextLevel = 0 } } + + return visited } diff --git a/usvm-util/src/test/kotlin/org/usvm/test/GraphUtilsLimitedBfsTests.kt b/usvm-util/src/test/kotlin/org/usvm/test/GraphUtilsLimitedBfsTests.kt new file mode 100644 index 0000000000..b0e2c17909 --- /dev/null +++ b/usvm-util/src/test/kotlin/org/usvm/test/GraphUtilsLimitedBfsTests.kt @@ -0,0 +1,34 @@ +package org.usvm.test + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import org.usvm.util.limitedBfsTraversal +import kotlin.test.assertEquals + +internal class GraphUtilsLimitedBfsTests { + + @ParameterizedTest + @MethodSource("limitedBfsTestCases") + fun limitedBfsTraversalTest(limit: Int, graph: SimpleGraph, startVertex: Int, expectedVisited: Set) { + val visited = limitedBfsTraversal(limit.toUInt(), listOf(startVertex), adjacentVertices = { graph.getAdjacentVertices(it).toSet() }) + assertEquals(expectedVisited, visited) + } + + companion object { + @JvmStatic + fun limitedBfsTestCases(): Collection { + return listOf( + Arguments.of(0, TestGraphs.graph1, 0, setOf(0)), + Arguments.of(1, TestGraphs.graph1, 0, setOf(0, 1, 7)), + Arguments.of(2, TestGraphs.graph1, 0, setOf(0, 1, 7, 2, 8, 6)), + Arguments.of(3, TestGraphs.graph1, 0, setOf(0, 1, 7, 2, 8, 6, 5, 3)), + Arguments.of(0, TestGraphs.graph1, 8, setOf(8)), + Arguments.of(1, TestGraphs.graph1, 8, setOf(8, 2, 6, 7)), + Arguments.of(2, TestGraphs.graph1, 8, setOf(8, 2, 6, 7, 0, 1, 5, 3)), + Arguments.of(3, TestGraphs.graph1, 8, setOf(8, 2, 6, 7, 0, 1, 5, 3, 4)), + Arguments.of(42, TestGraphs.graph1, 8, setOf(8, 2, 6, 7, 0, 1, 5, 3, 4)) + ) + } + } +} From 26ec5ac871ed2bc949555695f0b14f6f8f1f00fc Mon Sep 17 00:00:00 2001 From: Maksim Parshin Date: Wed, 6 Sep 2023 18:17:11 +0300 Subject: [PATCH 10/17] Filter actual overrides in JcCallGraphStatistics --- .../kotlin/org/usvm/machine/JcCallGraphStatistics.kt | 8 +++++--- usvm-jvm/src/main/kotlin/org/usvm/util/Utils.kt | 12 ++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt index d0dfd09439..386e246c0a 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt @@ -7,6 +7,7 @@ import org.jacodb.api.ext.findMethodOrNull import org.jacodb.api.ext.toType import org.usvm.statistics.distances.CallGraphStatistics import org.usvm.types.UTypeStream +import org.usvm.util.isDefinition import org.usvm.util.limitedBfsTraversal import java.util.concurrent.ConcurrentHashMap @@ -38,14 +39,15 @@ class JcCallGraphStatistics( return callees } - // TODO: check that the method was actually overridden or base implementation is used return typeStream .filterBySupertype(method.enclosingClass.toType()) .take(subclassesToTake) - .mapTo(callees) { - checkNotNull((it as? JcClassType)?.findMethodOrNull(method.name, method.description)?.method) { + .mapNotNullTo(callees) { + val override = checkNotNull((it as? JcClassType)?.findMethodOrNull(method.name, method.description)?.method) { "Cannot find overridden method $method in type $it" } + // Check that the method was actually overridden + if (override.isDefinition()) method else null } } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/util/Utils.kt b/usvm-jvm/src/main/kotlin/org/usvm/util/Utils.kt index 8919648d52..b6b415fed3 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/util/Utils.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/util/Utils.kt @@ -1,5 +1,6 @@ package org.usvm.util +import org.jacodb.api.JcMethod import org.jacodb.api.JcRefType import org.jacodb.api.JcType import org.usvm.UExpr @@ -18,3 +19,14 @@ fun JcContext.extractJcRefType(clazz: KClass<*>): JcRefType = extractJcType(claz fun UWritableMemory<*>.write(ref: ULValue<*, *>, value: UExpr<*>) { write(ref as ULValue<*, USort>, value as UExpr, value.uctx.trueExpr) } + +/** + * Checks if the method is the same as its definition (i.e. it is false for + * subclasses' methods which use base definition). + */ +fun JcMethod.isDefinition(): Boolean { + if (instList.size == 0) { + return true + } + return instList.first().location.method == this +} From c0adfd274d7c71dc9c3beb01bf983f401955e17b Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 6 Sep 2023 19:58:32 +0300 Subject: [PATCH 11/17] Rebase commit --- .../usvm/api/collection/ListCollectionApi.kt | 18 ++--- .../api/collection/ObjectMapCollectionApi.kt | 16 ++-- .../kotlin/org/usvm/ps/PathSelectorFactory.kt | 11 ++- .../distances/CallGraphStatisticsImpl.kt | 4 +- .../org/usvm/api/collections/ObjectMapTest.kt | 2 +- .../collections/SymbolicCollectionTestBase.kt | 3 +- .../usvm/api/collections/SymbolicListTest.kt | 2 +- .../org/usvm/machine/JcCallGraphStatistics.kt | 2 +- .../org/usvm/samples/JavaMethodTestRunner.kt | 7 +- .../org/usvm/test/GraphUtilsDistanceTests.kt | 50 +++++++++++++ .../usvm/test/GraphUtilsLimitedBfsTests.kt | 2 +- .../kotlin/org/usvm/test/MathUtilsTests.kt | 27 +++++++ .../test/kotlin/org/usvm/test/TestGraphs.kt | 74 +++++++++++++++++++ 13 files changed, 187 insertions(+), 31 deletions(-) create mode 100644 usvm-util/src/test/kotlin/org/usvm/test/GraphUtilsDistanceTests.kt create mode 100644 usvm-util/src/test/kotlin/org/usvm/test/MathUtilsTests.kt create mode 100644 usvm-util/src/test/kotlin/org/usvm/test/TestGraphs.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/api/collection/ListCollectionApi.kt b/usvm-core/src/main/kotlin/org/usvm/api/collection/ListCollectionApi.kt index e46eed6604..f73b2a6e56 100644 --- a/usvm-core/src/main/kotlin/org/usvm/api/collection/ListCollectionApi.kt +++ b/usvm-core/src/main/kotlin/org/usvm/api/collection/ListCollectionApi.kt @@ -15,7 +15,7 @@ import org.usvm.memory.map import org.usvm.uctx object ListCollectionApi { - fun UState.mkSymbolicList( + fun UState.mkSymbolicList( listType: ListType, ): UHeapRef = with(memory.ctx) { val ref = memory.alloc(listType) @@ -27,12 +27,12 @@ object ListCollectionApi { * List size may be incorrect for input lists. * Use [ensureListSizeCorrect] to guarantee that list size is correct. * */ - fun UState.symbolicListSize( + fun UState.symbolicListSize( listRef: UHeapRef, listType: ListType, ): USizeExpr = memory.readArrayLength(listRef, listType) - fun > StepScope.ensureListSizeCorrect( + fun > StepScope.ensureListSizeCorrect( listRef: UHeapRef, listType: ListType, ): Unit? { @@ -54,14 +54,14 @@ object ListCollectionApi { return Unit } - fun UState.symbolicListGet( + fun UState.symbolicListGet( listRef: UHeapRef, index: USizeExpr, listType: ListType, sort: Sort, ): UExpr = memory.readArrayIndex(listRef, index, listType, sort) - fun UState.symbolicListAdd( + fun UState.symbolicListAdd( listRef: UHeapRef, listType: ListType, sort: Sort, @@ -76,7 +76,7 @@ object ListCollectionApi { } } - fun UState.symbolicListSet( + fun UState.symbolicListSet( listRef: UHeapRef, listType: ListType, sort: Sort, @@ -86,7 +86,7 @@ object ListCollectionApi { memory.writeArrayIndex(listRef, index, listType, sort, value, guard = memory.ctx.trueExpr) } - fun UState.symbolicListInsert( + fun UState.symbolicListInsert( listRef: UHeapRef, listType: ListType, sort: Sort, @@ -116,7 +116,7 @@ object ListCollectionApi { memory.writeArrayLength(listRef, updatedSize, listType) } - fun UState.symbolicListRemove( + fun UState.symbolicListRemove( listRef: UHeapRef, listType: ListType, sort: Sort, @@ -142,7 +142,7 @@ object ListCollectionApi { memory.writeArrayLength(listRef, updatedSize, listType) } - fun UState.symbolicListCopyRange( + fun UState.symbolicListCopyRange( srcRef: UHeapRef, dstRef: UHeapRef, listType: ListType, diff --git a/usvm-core/src/main/kotlin/org/usvm/api/collection/ObjectMapCollectionApi.kt b/usvm-core/src/main/kotlin/org/usvm/api/collection/ObjectMapCollectionApi.kt index 5a67a83f2b..0787d70ebb 100644 --- a/usvm-core/src/main/kotlin/org/usvm/api/collection/ObjectMapCollectionApi.kt +++ b/usvm-core/src/main/kotlin/org/usvm/api/collection/ObjectMapCollectionApi.kt @@ -17,7 +17,7 @@ import org.usvm.memory.map import org.usvm.uctx object ObjectMapCollectionApi { - fun UState.mkSymbolicObjectMap( + fun UState.mkSymbolicObjectMap( mapType: MapType, ): UHeapRef = with(memory.ctx) { val ref = memory.alloc(mapType) @@ -31,12 +31,12 @@ object ObjectMapCollectionApi { * Use [ensureObjectMapSizeCorrect] to guarantee that map size is correct. * todo: input map size can be inconsistent with contains * */ - fun UState.symbolicObjectMapSize( + fun UState.symbolicObjectMapSize( mapRef: UHeapRef, mapType: MapType, ): USizeExpr = memory.read(UMapLengthLValue(mapRef, mapType)) - fun > StepScope.ensureObjectMapSizeCorrect( + fun > StepScope.ensureObjectMapSizeCorrect( mapRef: UHeapRef, mapType: MapType, ): Unit? { @@ -58,20 +58,20 @@ object ObjectMapCollectionApi { return Unit } - fun UState.symbolicObjectMapGet( + fun UState.symbolicObjectMapGet( mapRef: UHeapRef, key: UHeapRef, mapType: MapType, sort: Sort, ): UExpr = memory.read(URefMapEntryLValue(sort, mapRef, key, mapType)) - fun UState.symbolicObjectMapContains( + fun UState.symbolicObjectMapContains( mapRef: UHeapRef, key: UHeapRef, mapType: MapType, ): UBoolExpr = memory.read(URefSetEntryLValue(mapRef, key, mapType)) - fun UState.symbolicObjectMapPut( + fun UState.symbolicObjectMapPut( mapRef: UHeapRef, key: UHeapRef, value: UExpr, @@ -91,7 +91,7 @@ object ObjectMapCollectionApi { memory.write(UMapLengthLValue(mapRef, mapType), updatedSize, keyIsNew) } - fun UState.symbolicObjectMapRemove( + fun UState.symbolicObjectMapRemove( mapRef: UHeapRef, key: UHeapRef, mapType: MapType, @@ -108,7 +108,7 @@ object ObjectMapCollectionApi { memory.write(UMapLengthLValue(mapRef, mapType), updatedSize, keyIsInMap) } - fun UState.symbolicObjectMapMergeInto( + fun UState.symbolicObjectMapMergeInto( dstRef: UHeapRef, srcRef: UHeapRef, mapType: MapType, diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt index 91ddb184a3..1bdaefd748 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt @@ -1,26 +1,25 @@ package org.usvm.ps -import org.usvm.Location -import kotlin.math.max -import kotlin.random.Random import org.usvm.PathSelectionStrategy import org.usvm.PathSelectorCombinationStrategy import org.usvm.UMachineOptions import org.usvm.UPathSelector import org.usvm.UState import org.usvm.UTarget +import org.usvm.algorithms.DeterministicPriorityCollection +import org.usvm.algorithms.RandomizedPriorityCollection import org.usvm.statistics.ApplicationGraph import org.usvm.statistics.CoverageStatistics import org.usvm.statistics.distances.CallGraphStatistics import org.usvm.statistics.distances.CallStackDistanceCalculator import org.usvm.statistics.distances.CfgStatistics -import org.usvm.statistics.distances.MultiTargetDistanceCalculator import org.usvm.statistics.distances.InterprocDistance import org.usvm.statistics.distances.InterprocDistanceCalculator +import org.usvm.statistics.distances.MultiTargetDistanceCalculator import org.usvm.statistics.distances.ReachabilityKind -import org.usvm.util.DeterministicPriorityCollection -import org.usvm.util.RandomizedPriorityCollection import org.usvm.util.log2 +import kotlin.math.max +import kotlin.random.Random fun , State : UState<*, Method, Statement, *, Target, State>> createPathSelector( initialState: State, diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatisticsImpl.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatisticsImpl.kt index 342186350d..b585134bae 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatisticsImpl.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallGraphStatisticsImpl.kt @@ -1,8 +1,8 @@ package org.usvm.statistics.distances -import java.util.concurrent.ConcurrentHashMap +import org.usvm.algorithms.limitedBfsTraversal import org.usvm.statistics.ApplicationGraph -import org.usvm.util.limitedBfsTraversal +import java.util.concurrent.ConcurrentHashMap /** * [CallGraphStatistics] common implementation with thread-safe results caching. As it is language-agnostic, diff --git a/usvm-core/src/test/kotlin/org/usvm/api/collections/ObjectMapTest.kt b/usvm-core/src/test/kotlin/org/usvm/api/collections/ObjectMapTest.kt index 89319002de..dad8f471e5 100644 --- a/usvm-core/src/test/kotlin/org/usvm/api/collections/ObjectMapTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/api/collections/ObjectMapTest.kt @@ -335,7 +335,7 @@ class ObjectMapTest : SymbolicCollectionTestBase() { } } - private fun UState.fillMap( + private fun UState.fillMap( mapRef: UHeapRef, keys: List, startValueIdx: Int diff --git a/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt b/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt index c3373c09b6..44860942ce 100644 --- a/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt +++ b/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt @@ -13,6 +13,7 @@ import org.usvm.UComponents import org.usvm.UContext import org.usvm.UExpr import org.usvm.UState +import org.usvm.UTarget import org.usvm.constraints.UPathConstraints import org.usvm.memory.UMemory import org.usvm.model.buildTranslatorAndLazyDecoder @@ -57,7 +58,7 @@ abstract class SymbolicCollectionTestBase { ctx: UContext, pathConstraints: UPathConstraints, memory: UMemory, - ) : UState( + ) : UState( ctx, UCallStack(), pathConstraints, memory, emptyList(), ctx.mkInitialLocation() ) { diff --git a/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicListTest.kt b/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicListTest.kt index a07e5b4f8b..839ea02d8f 100644 --- a/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicListTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicListTest.kt @@ -130,7 +130,7 @@ class SymbolicListTest : SymbolicCollectionTestBase() { checkValues(listRef, listValues, initialSize) } - private fun UState.checkValues( + private fun UState.checkValues( listRef: UHeapRef, values: List, initialSize: USizeExpr diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt index 386e246c0a..7af906aded 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt @@ -5,10 +5,10 @@ import org.jacodb.api.JcMethod import org.jacodb.api.JcType import org.jacodb.api.ext.findMethodOrNull import org.jacodb.api.ext.toType +import org.usvm.algorithms.limitedBfsTraversal import org.usvm.statistics.distances.CallGraphStatistics import org.usvm.types.UTypeStream import org.usvm.util.isDefinition -import org.usvm.util.limitedBfsTraversal import java.util.concurrent.ConcurrentHashMap /** diff --git a/usvm-jvm/src/test/kotlin/org/usvm/samples/JavaMethodTestRunner.kt b/usvm-jvm/src/test/kotlin/org/usvm/samples/JavaMethodTestRunner.kt index fb32eb5bc3..bdd691376e 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/samples/JavaMethodTestRunner.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/samples/JavaMethodTestRunner.kt @@ -15,7 +15,12 @@ import org.usvm.machine.JcMachine import org.usvm.test.util.TestRunner import org.usvm.test.util.checkers.AnalysisResultsNumberMatcher import org.usvm.test.util.checkers.ignoreNumberOfAnalysisResults -import kotlin.reflect.* +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KFunction1 +import kotlin.reflect.KFunction2 +import kotlin.reflect.KFunction3 +import kotlin.reflect.KFunction4 import kotlin.reflect.full.instanceParameter import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaMethod diff --git a/usvm-util/src/test/kotlin/org/usvm/test/GraphUtilsDistanceTests.kt b/usvm-util/src/test/kotlin/org/usvm/test/GraphUtilsDistanceTests.kt new file mode 100644 index 0000000000..dc3af4fce6 --- /dev/null +++ b/usvm-util/src/test/kotlin/org/usvm/test/GraphUtilsDistanceTests.kt @@ -0,0 +1,50 @@ +package org.usvm.test + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import org.usvm.algorithms.findMinDistancesInUnweightedGraph +import kotlin.test.assertEquals + +internal class GraphUtilsDistanceTests { + + @ParameterizedTest + @MethodSource("distanceTestCases") + fun findShortestDistancesInUnweightedGraphTest(graph: SimpleGraph, startVertex: Int, expected: Map) { + val foundDistances = findMinDistancesInUnweightedGraph(startVertex, graph::getAdjacentVertices) + assertEquals(expected.size, foundDistances.size) + expected.forEach { (i, d) -> assertEquals(d, foundDistances[i]) } + } + + @ParameterizedTest + @MethodSource("distanceTestCases") + fun findShortestDistancesInUnweightedGraphWithCacheTest(graph: SimpleGraph, startVertex: Int, expected: Map) { + val cache = mutableMapOf>() + for (i in 0 until graph.vertexCount) { + if (i == startVertex) { continue } + val foundDistances = findMinDistancesInUnweightedGraph(i, graph::getAdjacentVertices, cache) + val foundWithoutCacheDistances = findMinDistancesInUnweightedGraph(i, graph::getAdjacentVertices) + foundWithoutCacheDistances.forEach { (i, d) -> assertEquals(d, foundDistances[i]) } + cache[i] = foundDistances + } + + val foundDistances = findMinDistancesInUnweightedGraph(startVertex, graph::getAdjacentVertices) + val foundWithoutCacheDistances = findMinDistancesInUnweightedGraph(startVertex, graph::getAdjacentVertices) + + assertEquals(expected.size, foundDistances.size) + foundWithoutCacheDistances.forEach { (i, d) -> assertEquals(d, foundDistances[i]) } + expected.forEach { (i, d) -> assertEquals(d, foundDistances[i]) } + } + + companion object { + @JvmStatic + fun distanceTestCases(): Collection { + return listOf( + Arguments.of(TestGraphs.graph1, 0, TestGraphs.graph1Expected), + Arguments.of(TestGraphs.graph1WithStandaloneVertices, 0, TestGraphs.graph1Expected), + Arguments.of(TestGraphs.graph1WithStandaloneVertices, 15, mapOf(15 to 0u)), + Arguments.of(TestGraphs.graph2, 0, TestGraphs.graph2Expected), + ) + } + } +} diff --git a/usvm-util/src/test/kotlin/org/usvm/test/GraphUtilsLimitedBfsTests.kt b/usvm-util/src/test/kotlin/org/usvm/test/GraphUtilsLimitedBfsTests.kt index b0e2c17909..83ab4b91e9 100644 --- a/usvm-util/src/test/kotlin/org/usvm/test/GraphUtilsLimitedBfsTests.kt +++ b/usvm-util/src/test/kotlin/org/usvm/test/GraphUtilsLimitedBfsTests.kt @@ -3,7 +3,7 @@ package org.usvm.test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource -import org.usvm.util.limitedBfsTraversal +import org.usvm.algorithms.limitedBfsTraversal import kotlin.test.assertEquals internal class GraphUtilsLimitedBfsTests { diff --git a/usvm-util/src/test/kotlin/org/usvm/test/MathUtilsTests.kt b/usvm-util/src/test/kotlin/org/usvm/test/MathUtilsTests.kt new file mode 100644 index 0000000000..17be483b83 --- /dev/null +++ b/usvm-util/src/test/kotlin/org/usvm/test/MathUtilsTests.kt @@ -0,0 +1,27 @@ +package org.usvm.test + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import org.usvm.util.log2 +import kotlin.math.floor +import kotlin.test.assertEquals + +class MathUtilsTests { + @ParameterizedTest + @ValueSource(strings = ["0", "1", "2", "3", "4", "64", "100", "2048", "11111111", "4294967294", "4294967295"]) + fun log2Test(nStr: String) { + val n = nStr.toUInt() + + if (n == UInt.MAX_VALUE) { + assertEquals(32u, log2(n)) + return + } + + if (n == 0u) { + assertEquals(0u, log2(n)) + return + } + + assertEquals(floor(kotlin.math.log2(nStr.toDouble())).toInt(), log2(n).toInt()) + } +} diff --git a/usvm-util/src/test/kotlin/org/usvm/test/TestGraphs.kt b/usvm-util/src/test/kotlin/org/usvm/test/TestGraphs.kt new file mode 100644 index 0000000000..54550d6d14 --- /dev/null +++ b/usvm-util/src/test/kotlin/org/usvm/test/TestGraphs.kt @@ -0,0 +1,74 @@ +package org.usvm.test + +class SimpleGraph(val vertexCount: Int) { + private val adjacencyLists = Array(vertexCount) { mutableSetOf(it) } + + fun addEdge(fromVertex: Int, toVertex: Int) { + adjacencyLists[fromVertex].add(toVertex) + adjacencyLists[toVertex].add(fromVertex) + } + + fun getAdjacentVertices(vertexIndex: Int): Sequence = adjacencyLists[vertexIndex].asSequence() +} + +object TestGraphs { + val graph1 = SimpleGraph(9).apply { + addEdge(0, 1) + addEdge(0, 7) + addEdge(1, 7) + addEdge(1, 2) + addEdge(7, 6) + addEdge(7, 8) + addEdge(2, 8) + addEdge(8, 6) + addEdge(2, 5) + addEdge(2, 3) + addEdge(6, 5) + addEdge(3, 5) + addEdge(3, 4) + addEdge(5, 4) + } + val graph1WithStandaloneVertices = SimpleGraph(30).apply { + addEdge(0, 1) + addEdge(0, 7) + addEdge(1, 7) + addEdge(1, 2) + addEdge(7, 6) + addEdge(7, 8) + addEdge(2, 8) + addEdge(8, 6) + addEdge(2, 5) + addEdge(2, 3) + addEdge(6, 5) + addEdge(3, 5) + addEdge(3, 4) + addEdge(5, 4) + } + val graph1Expected = mapOf( + 0 to 0u, + 1 to 1u, + 2 to 2u, + 3 to 3u, + 4 to 4u, + 5 to 3u, + 6 to 2u, + 7 to 1u, + 8 to 2u + ) + val graph2 = SimpleGraph(6).apply { + addEdge(0, 1) + addEdge(0, 2) + addEdge(0, 3) + addEdge(2, 4) + addEdge(3, 5) + addEdge(4, 5) + } + val graph2Expected = mapOf( + 0 to 0u, + 1 to 1u, + 2 to 1u, + 3 to 1u, + 4 to 2u, + 5 to 2u + ) +} From 90b1280fdb29441f6eb836bbb744fa57a3e09d26 Mon Sep 17 00:00:00 2001 From: Maksim Parshin Date: Thu, 7 Sep 2023 14:01:06 +0300 Subject: [PATCH 12/17] Fix JcCallGraphStatistics overrides resolving and add tests for it --- .../org/usvm/machine/JcCallGraphStatistics.kt | 28 ++++++---- .../machine/interpreter/JcExprResolver.kt | 11 ++-- .../JcBinaryOperator.kt | 4 +- .../JcOperatorUtils.kt | 2 +- .../JcUnaryOperator.kt | 2 +- .../kotlin/org/usvm/util/JcMethodUtils.kt | 28 ++++++++++ .../src/main/kotlin/org/usvm/util/Utils.kt | 12 ----- .../callgraph/CallGraphTestClass1.java | 12 +++++ .../callgraph/CallGraphTestClass2.java | 11 ++++ .../callgraph/CallGraphTestClass3.java | 16 ++++++ .../callgraph/CallGraphTestClass4.java | 9 ++++ .../callgraph/CallGraphTestInterface.java | 6 +++ .../machine/JcCallGraphStatisticsTests.kt | 53 +++++++++++++++++++ .../operators/JcBinaryOperatorTest.kt | 4 +- .../operators/JcOperatorTestData.kt | 2 +- .../operators/JcUnaryOperatorTest.kt | 4 +- 16 files changed, 167 insertions(+), 37 deletions(-) rename usvm-jvm/src/main/kotlin/org/usvm/machine/{operator => operators}/JcBinaryOperator.kt (98%) rename usvm-jvm/src/main/kotlin/org/usvm/machine/{operator => operators}/JcOperatorUtils.kt (98%) rename usvm-jvm/src/main/kotlin/org/usvm/machine/{operator => operators}/JcUnaryOperator.kt (98%) create mode 100644 usvm-jvm/src/main/kotlin/org/usvm/util/JcMethodUtils.kt create mode 100644 usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass1.java create mode 100644 usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass2.java create mode 100644 usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass3.java create mode 100644 usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass4.java create mode 100644 usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestInterface.java create mode 100644 usvm-jvm/src/test/kotlin/org/usvm/machine/JcCallGraphStatisticsTests.kt rename usvm-jvm/src/test/kotlin/org/usvm/{ => machine}/operators/JcBinaryOperatorTest.kt (99%) rename usvm-jvm/src/test/kotlin/org/usvm/{ => machine}/operators/JcOperatorTestData.kt (97%) rename usvm-jvm/src/test/kotlin/org/usvm/{ => machine}/operators/JcUnaryOperatorTest.kt (98%) diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt index 7af906aded..bfc4f67e5e 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt @@ -8,6 +8,7 @@ import org.jacodb.api.ext.toType import org.usvm.algorithms.limitedBfsTraversal import org.usvm.statistics.distances.CallGraphStatistics import org.usvm.types.UTypeStream +import org.usvm.util.canBeOverridden import org.usvm.util.isDefinition import java.util.concurrent.ConcurrentHashMap @@ -39,16 +40,25 @@ class JcCallGraphStatistics( return callees } - return typeStream - .filterBySupertype(method.enclosingClass.toType()) - .take(subclassesToTake) - .mapNotNullTo(callees) { - val override = checkNotNull((it as? JcClassType)?.findMethodOrNull(method.name, method.description)?.method) { - "Cannot find overridden method $method in type $it" - } - // Check that the method was actually overridden - if (override.isDefinition()) method else null + val overrides = mutableSetOf() + for (callee in callees) { + if (!callee.canBeOverridden()) { + continue } + + typeStream + .filterBySupertype(callee.enclosingClass.toType()) + .take(subclassesToTake) + .mapNotNullTo(overrides) { + val override = checkNotNull((it as? JcClassType)?.findMethodOrNull(callee.name, callee.description)?.method) { + "Cannot find overridden method $callee in type $it" + } + // Check that the method was actually overridden + if (override.isDefinition()) override else null + } + } + + return callees + overrides } override fun checkReachability(methodFrom: JcMethod, methodTo: JcMethod): Boolean = diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt index 76c649e1db..07600e9d0c 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt @@ -94,11 +94,11 @@ import org.usvm.collection.array.length.UArrayLengthLValue import org.usvm.collection.field.UFieldLValue import org.usvm.isTrue import org.usvm.machine.JcContext -import org.usvm.machine.operator.JcBinaryOperator -import org.usvm.machine.operator.JcUnaryOperator -import org.usvm.machine.operator.ensureBvExpr -import org.usvm.machine.operator.mkNarrow -import org.usvm.machine.operator.wideTo32BitsIfNeeded +import org.usvm.machine.operators.JcBinaryOperator +import org.usvm.machine.operators.JcUnaryOperator +import org.usvm.machine.operators.ensureBvExpr +import org.usvm.machine.operators.mkNarrow +import org.usvm.machine.operators.wideTo32BitsIfNeeded import org.usvm.machine.state.JcMethodResult import org.usvm.machine.state.JcState import org.usvm.machine.state.addConcreteMethodCallStmt @@ -106,6 +106,7 @@ import org.usvm.machine.state.addVirtualMethodCallStmt import org.usvm.machine.state.throwExceptionWithoutStackFrameDrop import org.usvm.memory.ULValue import org.usvm.memory.URegisterStackLValue +import org.usvm.util.extractJcRefType import org.usvm.util.write /** diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcBinaryOperator.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcBinaryOperator.kt similarity index 98% rename from usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcBinaryOperator.kt rename to usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcBinaryOperator.kt index 2d87f3df86..36b1c9e9a4 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcBinaryOperator.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcBinaryOperator.kt @@ -1,4 +1,4 @@ -package org.usvm.machine.operator +package org.usvm.machine.operators import io.ksmt.utils.asExpr import io.ksmt.utils.cast @@ -13,7 +13,7 @@ import org.usvm.machine.jctx import org.usvm.uctx /** - * An util class for performing binary operations on expressions. + * A util class for performing binary operations on expressions. */ sealed class JcBinaryOperator( val onBool: JcContext.(UExpr, UExpr) -> UExpr = shouldNotBeCalled, diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcOperatorUtils.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcOperatorUtils.kt similarity index 98% rename from usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcOperatorUtils.kt rename to usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcOperatorUtils.kt index 18d480f65c..37f09db150 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcOperatorUtils.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcOperatorUtils.kt @@ -1,4 +1,4 @@ -package org.usvm.machine.operator +package org.usvm.machine.operators import io.ksmt.sort.KBoolSort import io.ksmt.sort.KBvSort diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcUnaryOperator.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcUnaryOperator.kt similarity index 98% rename from usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcUnaryOperator.kt rename to usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcUnaryOperator.kt index 796be91c32..313e2b99c6 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcUnaryOperator.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcUnaryOperator.kt @@ -1,4 +1,4 @@ -package org.usvm.machine.operator +package org.usvm.machine.operators import io.ksmt.expr.KExpr import io.ksmt.utils.cast diff --git a/usvm-jvm/src/main/kotlin/org/usvm/util/JcMethodUtils.kt b/usvm-jvm/src/main/kotlin/org/usvm/util/JcMethodUtils.kt new file mode 100644 index 0000000000..f0a0da1d1c --- /dev/null +++ b/usvm-jvm/src/main/kotlin/org/usvm/util/JcMethodUtils.kt @@ -0,0 +1,28 @@ +package org.usvm.util + +import org.jacodb.api.JcMethod + +/** + * Checks if the method is the same as its definition (i.e. it is false for + * subclasses' methods which use base definition). + */ +fun JcMethod.isDefinition(): Boolean { + if (instList.size == 0) { + return true + } + return instList.first().location.method == this +} + +/** + * Checks if the method can be overridden: + * - it isn't static; + * - it isn't a constructor; + * - it isn't final; + * - it isn't private; + * - its enclosing class isn't final. + */ +fun JcMethod.canBeOverridden(): Boolean = + /* + https://stackoverflow.com/a/30416883 + */ + !isStatic && !isConstructor && !isFinal && !isPrivate && !enclosingClass.isFinal diff --git a/usvm-jvm/src/main/kotlin/org/usvm/util/Utils.kt b/usvm-jvm/src/main/kotlin/org/usvm/util/Utils.kt index b6b415fed3..8919648d52 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/util/Utils.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/util/Utils.kt @@ -1,6 +1,5 @@ package org.usvm.util -import org.jacodb.api.JcMethod import org.jacodb.api.JcRefType import org.jacodb.api.JcType import org.usvm.UExpr @@ -19,14 +18,3 @@ fun JcContext.extractJcRefType(clazz: KClass<*>): JcRefType = extractJcType(claz fun UWritableMemory<*>.write(ref: ULValue<*, *>, value: UExpr<*>) { write(ref as ULValue<*, USort>, value as UExpr, value.uctx.trueExpr) } - -/** - * Checks if the method is the same as its definition (i.e. it is false for - * subclasses' methods which use base definition). - */ -fun JcMethod.isDefinition(): Boolean { - if (instList.size == 0) { - return true - } - return instList.first().location.method == this -} diff --git a/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass1.java b/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass1.java new file mode 100644 index 0000000000..b7018233bd --- /dev/null +++ b/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass1.java @@ -0,0 +1,12 @@ +package org.usvm.samples.callgraph; + +public class CallGraphTestClass1 { + + public int A() { + return 1; + } + + public final int B() { + return 2; + } +} diff --git a/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass2.java b/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass2.java new file mode 100644 index 0000000000..fafb0c110a --- /dev/null +++ b/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass2.java @@ -0,0 +1,11 @@ +package org.usvm.samples.callgraph; + +public class CallGraphTestClass2 extends CallGraphTestClass1 { + + private CallGraphTestInterface callGraphTestInterface; + + @Override + public int A() { + return callGraphTestInterface.A() + 3; + } +} diff --git a/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass3.java b/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass3.java new file mode 100644 index 0000000000..dc826ffdc6 --- /dev/null +++ b/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass3.java @@ -0,0 +1,16 @@ +package org.usvm.samples.callgraph; + +public class CallGraphTestClass3 { + + public int C(CallGraphTestClass1 callGraphTestClass1) { + return callGraphTestClass1.A(); + } + + public int D(CallGraphTestInterface callGraphTestInterface) { + return callGraphTestInterface.A(); + } + + public int E(CallGraphTestClass1 callGraphTestClass1) { + return callGraphTestClass1.B(); + } +} diff --git a/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass4.java b/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass4.java new file mode 100644 index 0000000000..d17b300f35 --- /dev/null +++ b/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestClass4.java @@ -0,0 +1,9 @@ +package org.usvm.samples.callgraph; + +public class CallGraphTestClass4 implements CallGraphTestInterface { + + @Override + public int A() { + return 4; + } +} diff --git a/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestInterface.java b/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestInterface.java new file mode 100644 index 0000000000..ca1862132e --- /dev/null +++ b/usvm-jvm/src/samples/java/org/usvm/samples/callgraph/CallGraphTestInterface.java @@ -0,0 +1,6 @@ +package org.usvm.samples.callgraph; + +public interface CallGraphTestInterface { + + int A(); +} diff --git a/usvm-jvm/src/test/kotlin/org/usvm/machine/JcCallGraphStatisticsTests.kt b/usvm-jvm/src/test/kotlin/org/usvm/machine/JcCallGraphStatisticsTests.kt new file mode 100644 index 0000000000..9e362e4d04 --- /dev/null +++ b/usvm-jvm/src/test/kotlin/org/usvm/machine/JcCallGraphStatisticsTests.kt @@ -0,0 +1,53 @@ +package org.usvm.machine + +import org.junit.jupiter.api.Test +import org.usvm.samples.JavaMethodTestRunner +import org.usvm.samples.callgraph.CallGraphTestClass1 +import org.usvm.samples.callgraph.CallGraphTestClass2 +import org.usvm.samples.callgraph.CallGraphTestClass3 +import org.usvm.samples.callgraph.CallGraphTestClass4 +import org.usvm.util.getJcMethod +import kotlin.test.assertTrue + +class JcCallGraphStatisticsTests : JavaMethodTestRunner() { + + private val appGraph = JcApplicationGraph(cp) + private val typeStream = JcTypeSystem(cp).topTypeStream() + private val statistics = JcCallGraphStatistics(5u, appGraph, typeStream, 100) + + @Test + fun `base method is reachable`() { + val methodFrom = cp.getJcMethod(CallGraphTestClass3::C) + val methodTo = cp.getJcMethod(CallGraphTestClass1::A) + assertTrue { statistics.checkReachability(methodFrom, methodTo) } + } + + @Test + fun `method override is reachable`() { + val methodFrom = cp.getJcMethod(CallGraphTestClass3::C) + val methodTo = cp.getJcMethod(CallGraphTestClass2::A) + assertTrue { statistics.checkReachability(methodFrom, methodTo) } + } + + @Test + fun `interface implementation is reachable`() { + val methodFrom = cp.getJcMethod(CallGraphTestClass3::D) + val methodTo = cp.getJcMethod(CallGraphTestClass4::A) + assertTrue { statistics.checkReachability(methodFrom, methodTo) } + } + + @Test + fun `final method is reachable`() { + val methodFrom = cp.getJcMethod(CallGraphTestClass3::E) + val methodTo = cp.getJcMethod(CallGraphTestClass1::B) + assertTrue { statistics.checkReachability(methodFrom, methodTo) } + } + + // CallGraphTestClass3::C -> CallGraphTestClass2::A -> CallGraphTestClass4::A + @Test + fun `transitive reachability test`() { + val methodFrom = cp.getJcMethod(CallGraphTestClass3::C) + val methodTo = cp.getJcMethod(CallGraphTestClass4::A) + assertTrue { statistics.checkReachability(methodFrom, methodTo) } + } +} diff --git a/usvm-jvm/src/test/kotlin/org/usvm/operators/JcBinaryOperatorTest.kt b/usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcBinaryOperatorTest.kt similarity index 99% rename from usvm-jvm/src/test/kotlin/org/usvm/operators/JcBinaryOperatorTest.kt rename to usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcBinaryOperatorTest.kt index b7fe09dfc1..c5f185c01e 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/operators/JcBinaryOperatorTest.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcBinaryOperatorTest.kt @@ -1,4 +1,4 @@ -package org.usvm.operators +package org.usvm.machine.operators import io.mockk.mockk import org.junit.jupiter.api.BeforeEach @@ -13,8 +13,6 @@ import org.usvm.machine.extractDouble import org.usvm.machine.extractFloat import org.usvm.machine.extractInt import org.usvm.machine.extractLong -import org.usvm.machine.operator.JcBinaryOperator -import org.usvm.machine.operator.wideTo32BitsIfNeeded import kotlin.test.assertEquals class JcBinaryOperatorTest { diff --git a/usvm-jvm/src/test/kotlin/org/usvm/operators/JcOperatorTestData.kt b/usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcOperatorTestData.kt similarity index 97% rename from usvm-jvm/src/test/kotlin/org/usvm/operators/JcOperatorTestData.kt rename to usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcOperatorTestData.kt index 7a20ea3727..4af367aaab 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/operators/JcOperatorTestData.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcOperatorTestData.kt @@ -1,4 +1,4 @@ -package org.usvm.operators +package org.usvm.machine.operators val byteData = listOf( 0.toByte(), diff --git a/usvm-jvm/src/test/kotlin/org/usvm/operators/JcUnaryOperatorTest.kt b/usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcUnaryOperatorTest.kt similarity index 98% rename from usvm-jvm/src/test/kotlin/org/usvm/operators/JcUnaryOperatorTest.kt rename to usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcUnaryOperatorTest.kt index 4f36b11546..5b63652abf 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/operators/JcUnaryOperatorTest.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcUnaryOperatorTest.kt @@ -1,4 +1,4 @@ -package org.usvm.operators +package org.usvm.machine.operators import io.mockk.mockk import org.junit.jupiter.api.BeforeEach @@ -14,8 +14,6 @@ import org.usvm.machine.extractFloat import org.usvm.machine.extractInt import org.usvm.machine.extractLong import org.usvm.machine.extractShort -import org.usvm.machine.operator.JcUnaryOperator -import org.usvm.machine.operator.wideTo32BitsIfNeeded import kotlin.test.assertEquals class JcUnaryOperatorTest { From c4e9d4531745fe278ca9eccb170b90ac81df5461 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 13 Sep 2023 16:36:57 +0300 Subject: [PATCH 13/17] Rebase commit --- .../usvm/statistics/collectors/TargetsReachedStatesCollector.kt | 2 +- usvm-jvm/src/main/kotlin/org/usvm/machine/JcContext.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt index 9913c2fbe2..9dca452637 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt @@ -10,7 +10,7 @@ class TargetsReachedStatesCollector> : Stat private val mutableCollectedStates = mutableListOf() override val collectedStates: List = mutableCollectedStates - override fun onStateTerminated(state: State) { + override fun onStateTerminated(state: State, stateReachable: Boolean) { if (state.reachedTerminalTargets.isNotEmpty()) { mutableCollectedStates.add(state) } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcContext.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcContext.kt index a35140c3b0..b3e3c3eda9 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcContext.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcContext.kt @@ -22,7 +22,6 @@ import org.jacodb.api.ext.void import org.jacodb.impl.bytecode.JcFieldImpl import org.jacodb.impl.types.FieldInfo import org.usvm.UContext -import org.usvm.machine.interpreter.CompositeJcInterpreterObserver import org.usvm.util.extractJcRefType class JcContext( From bf9230e9bed90ab9f5c9232db8540d1fb31d714e Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 13 Sep 2023 16:56:01 +0300 Subject: [PATCH 14/17] Replace locations with statements --- .../src/main/kotlin/org/usvm/Location.kt | 3 --- usvm-core/src/main/kotlin/org/usvm/Targets.kt | 2 +- .../kotlin/org/usvm/ps/PathSelectorFactory.kt | 22 +++++++++++++------ .../org/usvm/statistics/CoverageStatistics.kt | 5 ++--- .../distances/CallStackDistanceCalculator.kt | 18 ++++++++------- .../distances/DistanceCalculator.kt | 11 +++++----- .../distances/InterprocDistanceCalculator.kt | 10 ++++----- .../src/test/kotlin/org/usvm/TestUtil.kt | 2 +- .../CallStackDistanceCalculatorTests.kt | 22 ++++++++++--------- .../InterprocDistanceCalculatorTests.kt | 5 ++--- .../kotlin/org/usvm/api/targets/JcTarget.kt | 3 +-- .../kotlin/org/usvm/machine/SampleTarget.kt | 3 +-- 12 files changed, 55 insertions(+), 51 deletions(-) delete mode 100644 usvm-core/src/main/kotlin/org/usvm/Location.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/Location.kt b/usvm-core/src/main/kotlin/org/usvm/Location.kt deleted file mode 100644 index f2e8ccc9dc..0000000000 --- a/usvm-core/src/main/kotlin/org/usvm/Location.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.usvm - -data class Location(val method: Method, val statement: Statement) diff --git a/usvm-core/src/main/kotlin/org/usvm/Targets.kt b/usvm-core/src/main/kotlin/org/usvm/Targets.kt index 27f4bf676a..87f5c4e73e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Targets.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Targets.kt @@ -16,7 +16,7 @@ abstract class UTarget? = null + val location: Statement? = null ) { private val childrenImpl = mutableListOf() private var parent: Target? = null diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt index 1bdaefd748..3ea1422f30 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt @@ -53,11 +53,13 @@ fun , Stat PathSelectionStrategy.CLOSEST_TO_UNCOVERED -> createClosestToUncoveredPathSelector( requireNotNull(coverageStatistics()) { "Coverage statistics is required for closest to uncovered path selector" }, - requireNotNull(cfgStatistics()) { "CFG statistics is required for closest to uncovered path selector" } + requireNotNull(cfgStatistics()) { "CFG statistics is required for closest to uncovered path selector" }, + applicationGraph ) PathSelectionStrategy.CLOSEST_TO_UNCOVERED_RANDOM -> createClosestToUncoveredPathSelector( requireNotNull(coverageStatistics()) { "Coverage statistics is required for closest to uncovered path selector" }, requireNotNull(cfgStatistics()) { "CFG statistics is required for closest to uncovered path selector" }, + applicationGraph, random ) @@ -74,10 +76,12 @@ fun , Stat ) PathSelectionStrategy.TARGETED_CALL_STACK_LOCAL -> createTargetedPathSelector( - requireNotNull(cfgStatistics()) { "CFG statistics is required for targeted call stack local path selector" } + requireNotNull(cfgStatistics()) { "CFG statistics is required for targeted call stack local path selector" }, + applicationGraph ) PathSelectionStrategy.TARGETED_CALL_STACK_LOCAL_RANDOM -> createTargetedPathSelector( requireNotNull(cfgStatistics()) { "CFG statistics is required for targeted call stack local path selector" }, + applicationGraph, random ) } @@ -147,11 +151,13 @@ private fun > createDepthPathSelector(rando private fun > createClosestToUncoveredPathSelector( coverageStatistics: CoverageStatistics, cfgStatistics: CfgStatistics, + applicationGraph: ApplicationGraph, random: Random? = null, ): UPathSelector { val distanceCalculator = CallStackDistanceCalculator( targets = coverageStatistics.getUncoveredStatements(), - cfgStatistics = cfgStatistics + cfgStatistics = cfgStatistics, + applicationGraph ) coverageStatistics.addOnCoveredObserver { _, method, statement -> distanceCalculator.removeTarget(method, statement) } @@ -187,12 +193,14 @@ private fun , State : UState<*, Method, Statement, *, Target, State>> createTargetedPathSelector( cfgStatistics: CfgStatistics, + applicationGraph: ApplicationGraph, random: Random? = null, ): UPathSelector { - val distanceCalculator = MultiTargetDistanceCalculator { loc -> + val distanceCalculator = MultiTargetDistanceCalculator { loc -> CallStackDistanceCalculator( targets = listOf(loc), - cfgStatistics = cfgStatistics + cfgStatistics = cfgStatistics, + applicationGraph = applicationGraph ) } @@ -247,9 +255,9 @@ internal fun , random: Random? = null, ): UPathSelector { - val distanceCalculator = MultiTargetDistanceCalculator { loc -> + val distanceCalculator = MultiTargetDistanceCalculator { stmt -> InterprocDistanceCalculator( - targetLocation = loc, + targetLocation = stmt, applicationGraph = applicationGraph, cfgStatistics = cfgStatistics, callGraphStatistics = callGraphStatistics diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt index 5821d068ff..d2751472f8 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt @@ -1,6 +1,5 @@ package org.usvm.statistics -import org.usvm.Location import org.usvm.UState import org.usvm.algorithms.bfsTraversal import java.util.concurrent.ConcurrentHashMap @@ -86,8 +85,8 @@ class CoverageStatistics> { - return uncoveredStatements.flatMap { kvp -> kvp.value.map { Location(kvp.key, it) } } + fun getUncoveredStatements(): Collection { + return uncoveredStatements.values.flatten() } /** diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt index 46b86acb75..425dc16407 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt @@ -1,8 +1,8 @@ package org.usvm.statistics.distances -import org.usvm.Location -import kotlin.math.min import org.usvm.UCallStack +import org.usvm.statistics.ApplicationGraph +import kotlin.math.min /** * Calculates shortest distances from location (represented as statement and call stack) to the set of targets @@ -15,18 +15,20 @@ import org.usvm.UCallStack * @param cfgStatistics [CfgStatistics] instance used to calculate local distances on each frame. */ class CallStackDistanceCalculator( - targets: Collection>, - private val cfgStatistics: CfgStatistics + targets: Collection, + private val cfgStatistics: CfgStatistics, + applicationGraph: ApplicationGraph ) : DistanceCalculator { // TODO: optimize for single target case - private val targetsByMethod = HashMap>() - private val minLocalDistanceToTargetCache = HashMap>() + private val targetsByMethod = hashMapOf>() + private val minLocalDistanceToTargetCache = hashMapOf>() init { - for ((method, stmt) in targets) { + for (target in targets) { + val method = applicationGraph.methodOf(target) val statements = targetsByMethod.computeIfAbsent(method) { hashSetOf() } - statements.add(stmt) + statements.add(target) } } diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt index b39160c9e1..35d3143eed 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/DistanceCalculator.kt @@ -1,6 +1,5 @@ package org.usvm.statistics.distances -import org.usvm.Location import org.usvm.UCallStack /** @@ -20,12 +19,12 @@ fun interface DistanceCalculator { * to calculate distances to arbitrary targets. */ class MultiTargetDistanceCalculator( - private val getDistanceCalculator: (Location) -> DistanceCalculator + private val getDistanceCalculator: (Statement) -> DistanceCalculator ) { - private val calculatorsByTarget = HashMap, DistanceCalculator>() + private val calculatorsByTarget = hashMapOf>() - // TODO: use - fun removeTargetFromCache(target: Location): Boolean { + // TODO: think later about better memory management using this function + fun removeTargetFromCache(target: Statement): Boolean { return calculatorsByTarget.remove(target) != null } @@ -35,7 +34,7 @@ class MultiTargetDistanceCalculator( fun calculateDistance( currentStatement: Statement, callStack: UCallStack, - target: Location + target: Statement ): Distance { val calculator = calculatorsByTarget.computeIfAbsent(target) { getDistanceCalculator(it) } return calculator.calculateDistance(currentStatement, callStack) diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt index b18e14bf0e..c021e70260 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt @@ -1,6 +1,5 @@ package org.usvm.statistics.distances -import org.usvm.Location import org.usvm.UCallStack import org.usvm.statistics.ApplicationGraph @@ -54,7 +53,7 @@ class InterprocDistance(val distance: UInt, reachabilityKind: ReachabilityKind) // TODO: give priority to paths without calls // TODO: add new targets according to the path? internal class InterprocDistanceCalculator( - private val targetLocation: Location, + private val targetLocation: Statement, private val applicationGraph: ApplicationGraph, private val cfgStatistics: CfgStatistics, private val callGraphStatistics: CallGraphStatistics @@ -63,8 +62,9 @@ internal class InterprocDistanceCalculator( private val frameDistanceCache = HashMap>() private fun calculateFrameDistance(method: Method, statement: Statement): InterprocDistance { - if (method == targetLocation.method) { - val localDistance = cfgStatistics.getShortestDistance(method, statement, targetLocation.statement) + val targetLocationMethod = applicationGraph.methodOf(targetLocation) + if (method == targetLocationMethod) { + val localDistance = cfgStatistics.getShortestDistance(method, statement, targetLocation) if (localDistance != UInt.MAX_VALUE) { return InterprocDistance(localDistance, ReachabilityKind.LOCAL) } @@ -87,7 +87,7 @@ internal class InterprocDistanceCalculator( } if (applicationGraph.callees(statementOfMethod).any { - callGraphStatistics.checkReachability(it, targetLocation.method) + callGraphStatistics.checkReachability(it, targetLocationMethod) }) { minDistanceToCall = distanceToCall } diff --git a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt index 9e25236b58..f060dfde0c 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt @@ -30,7 +30,7 @@ internal fun pseudoRandom(i: Int): Int { } internal class TestTarget(method: String, offset: Int) : UTarget( - Location(method, TestInstruction(method, offset)) + TestInstruction(method, offset) ) { fun reach(state: TestState) { propagate(state) diff --git a/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt b/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt index f10c43cc95..c8dfa6b332 100644 --- a/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/statistics/CallStackDistanceCalculatorTests.kt @@ -1,7 +1,8 @@ package org.usvm.statistics +import io.mockk.every +import io.mockk.mockk import org.junit.jupiter.api.Test -import org.usvm.Location import org.usvm.UCallStack import org.usvm.statistics.distances.CallStackDistanceCalculator import org.usvm.statistics.distances.CfgStatistics @@ -60,15 +61,13 @@ internal class CallStackDistanceCalculatorTests { override fun getShortestDistanceToExit(method: String, stmtFrom: Int): UInt = 1u } + val applicationGraph = mockk>() + every { applicationGraph.methodOf(any()) } returns "A" + val calculator = CallStackDistanceCalculator( - setOf( - Location("A", 2), - Location("A", 3), - Location("A", 4), - Location("A", 5), - Location("A", 6) - ), - cfgStatistics + setOf(2, 3, 4, 5, 6), + cfgStatistics, + applicationGraph ) val currentStatement = 1 @@ -104,8 +103,11 @@ internal class CallStackDistanceCalculatorTests { callStack.push("B", 3) callStack.push("C", 2) + val applicationGraph = mockk>() + every { applicationGraph.methodOf(node = 4) } returns "C" + val calculator = - CallStackDistanceCalculator(setOf(Location("C", 4)), cfgStatistics) + CallStackDistanceCalculator(setOf(4), cfgStatistics, applicationGraph) assertEquals(10u, calculator.calculateDistance(currentStatment, callStack)) calculator.removeTarget("C", 4) diff --git a/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt b/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt index ce644b8fee..e7eb41d187 100644 --- a/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/statistics/InterprocDistanceCalculatorTests.kt @@ -1,10 +1,8 @@ package org.usvm.statistics -import kotlin.test.assertEquals import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource -import org.usvm.Location import org.usvm.TestInstruction import org.usvm.UCallStack import org.usvm.appGraph @@ -15,6 +13,7 @@ import org.usvm.statistics.distances.InterprocDistance import org.usvm.statistics.distances.InterprocDistanceCalculator import org.usvm.statistics.distances.PlainCallGraphStatistics import org.usvm.statistics.distances.ReachabilityKind +import kotlin.test.assertEquals class InterprocDistanceCalculatorTests { @@ -179,7 +178,7 @@ class InterprocDistanceCalculatorTests { } val calculator = InterprocDistanceCalculator( - Location(targetLoc.method, targetLoc), + targetLoc, appGraph1, cfgStatistics, callGraphStatistics diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt index cef6959d61..ae78108e2c 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt @@ -2,7 +2,6 @@ package org.usvm.api.targets import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst -import org.usvm.Location import org.usvm.UTarget import org.usvm.machine.state.JcState @@ -10,5 +9,5 @@ import org.usvm.machine.state.JcState * Base class for JcMachine targets. */ abstract class JcTarget( - location: Location? = null + location: JcInst? = null ) : UTarget(location) diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt index c178943c3d..15aa00b098 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt @@ -1,6 +1,5 @@ package org.usvm.machine -import org.usvm.Location import org.usvm.UTarget import org.usvm.language.Method import org.usvm.language.Stmt @@ -8,4 +7,4 @@ import org.usvm.language.Stmt /** * Base class for SampleMachine targets. */ -abstract class SampleTarget(location: Location, Stmt>) : UTarget, Stmt, SampleTarget, SampleState>(location) +abstract class SampleTarget(location: Stmt) : UTarget, Stmt, SampleTarget, SampleState>(location) From ee4978697b4b7897608b760bcb136798d72bea76 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 13 Sep 2023 17:02:21 +0300 Subject: [PATCH 15/17] Remove the Method generic from the targets --- usvm-core/src/main/kotlin/org/usvm/State.kt | 8 +++++--- usvm-core/src/main/kotlin/org/usvm/Targets.kt | 7 ++++--- .../src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt | 6 +++--- .../kotlin/org/usvm/stopstrategies/StopStrategyFactory.kt | 2 +- .../org/usvm/stopstrategies/TargetsReachedStopStrategy.kt | 2 +- usvm-core/src/test/kotlin/org/usvm/TestUtil.kt | 2 +- .../usvm/api/collections/SymbolicCollectionTestBase.kt | 2 +- usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt | 3 +-- .../src/main/kotlin/org/usvm/machine/SampleTarget.kt | 3 +-- 9 files changed, 18 insertions(+), 17 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/State.kt b/usvm-core/src/main/kotlin/org/usvm/State.kt index 8a86b7c57b..824eb1680d 100644 --- a/usvm-core/src/main/kotlin/org/usvm/State.kt +++ b/usvm-core/src/main/kotlin/org/usvm/State.kt @@ -11,7 +11,7 @@ import org.usvm.solver.UUnsatResult typealias StateId = UInt -abstract class UState, State : UState>( +abstract class UState( // TODO: add interpreter-specific information ctx: UContext, open val callStack: UCallStack, @@ -19,8 +19,10 @@ abstract class UState, open var models: List>, open var pathLocation: PathsTrieNode, - targets: List = emptyList() -) { + targets: List = emptyList(), +) where Context : UContext, + Target : UTarget, + State : UState { /** * Deterministic state id. * TODO: Can be replaced with overridden hashCode diff --git a/usvm-core/src/main/kotlin/org/usvm/Targets.kt b/usvm-core/src/main/kotlin/org/usvm/Targets.kt index 87f5c4e73e..e66b7e81e0 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Targets.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Targets.kt @@ -12,12 +12,13 @@ package org.usvm * a state which has reached the target which has no children, it is logically removed from the targets tree. * The other states ignore such removed targets. */ -abstract class UTarget, State : UState<*, Method, Statement, *, Target, State>>( +abstract class UTarget( /** * Optional location of the target. */ - val location: Statement? = null -) { + val location: Statement? = null, +) where Target : UTarget, + State : UState<*, *, Statement, *, Target, State> { private val childrenImpl = mutableListOf() private var parent: Target? = null diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt index 3ea1422f30..321c04ff56 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt @@ -21,7 +21,7 @@ import org.usvm.util.log2 import kotlin.math.max import kotlin.random.Random -fun , State : UState<*, Method, Statement, *, Target, State>> createPathSelector( +fun , State : UState<*, Method, Statement, *, Target, State>> createPathSelector( initialState: State, options: UMachineOptions, applicationGraph: ApplicationGraph, @@ -191,7 +191,7 @@ private fun , State : UState<*, Method, Statement, *, Target, State>> createTargetedPathSelector( +internal fun , State : UState<*, Method, Statement, *, Target, State>> createTargetedPathSelector( cfgStatistics: CfgStatistics, applicationGraph: ApplicationGraph, random: Random? = null, @@ -249,7 +249,7 @@ private fun InterprocDistance.logWeight(): UInt { return weight } -internal fun , State : UState<*, Method, Statement, *, Target, State>> createTargetedPathSelector( +internal fun , State : UState<*, Method, Statement, *, Target, State>> createTargetedPathSelector( cfgStatistics: CfgStatistics, callGraphStatistics: CallGraphStatistics, applicationGraph: ApplicationGraph, diff --git a/usvm-core/src/main/kotlin/org/usvm/stopstrategies/StopStrategyFactory.kt b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/StopStrategyFactory.kt index 27a67c2d5e..580cc1c9d4 100644 --- a/usvm-core/src/main/kotlin/org/usvm/stopstrategies/StopStrategyFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/StopStrategyFactory.kt @@ -6,7 +6,7 @@ import org.usvm.statistics.CoverageStatistics fun createStopStrategy( options: UMachineOptions, - targets: Collection>, + targets: Collection>, coverageStatistics: () -> CoverageStatistics<*, *, *>? = { null }, getCollectedStatesCount: (() -> Int)? = null, ) : StopStrategy { diff --git a/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt index ceca4c33ff..18e15df004 100644 --- a/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt +++ b/usvm-core/src/main/kotlin/org/usvm/stopstrategies/TargetsReachedStopStrategy.kt @@ -5,6 +5,6 @@ import org.usvm.UTarget /** * A stop strategy which stops when all terminal targets in [targets] are reached. */ -class TargetsReachedStopStrategy(private val targets: Collection>) : StopStrategy { +class TargetsReachedStopStrategy(private val targets: Collection>) : StopStrategy { override fun shouldStop(): Boolean = targets.all { it.isRemoved } } diff --git a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt index f060dfde0c..d8e4f8ea2d 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt @@ -29,7 +29,7 @@ internal fun pseudoRandom(i: Int): Int { return res } -internal class TestTarget(method: String, offset: Int) : UTarget( +internal class TestTarget(method: String, offset: Int) : UTarget( TestInstruction(method, offset) ) { fun reach(state: TestState) { diff --git a/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt b/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt index 44860942ce..b69e338cf9 100644 --- a/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt +++ b/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt @@ -52,7 +52,7 @@ abstract class SymbolicCollectionTestBase { scope = StepScope(StateStub(ctx, pathConstraints, memory)) } - class TargetStub : UTarget() + class TargetStub : UTarget() class StateStub( ctx: UContext, diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt index ae78108e2c..b6fea3e074 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/targets/JcTarget.kt @@ -1,6 +1,5 @@ package org.usvm.api.targets -import org.jacodb.api.JcMethod import org.jacodb.api.cfg.JcInst import org.usvm.UTarget import org.usvm.machine.state.JcState @@ -10,4 +9,4 @@ import org.usvm.machine.state.JcState */ abstract class JcTarget( location: JcInst? = null -) : UTarget(location) +) : UTarget(location) diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt index 15aa00b098..9f76e10b48 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleTarget.kt @@ -1,10 +1,9 @@ package org.usvm.machine import org.usvm.UTarget -import org.usvm.language.Method import org.usvm.language.Stmt /** * Base class for SampleMachine targets. */ -abstract class SampleTarget(location: Stmt) : UTarget, Stmt, SampleTarget, SampleState>(location) +abstract class SampleTarget(location: Stmt) : UTarget(location) From b026720bbfb47dc385619a7a70127b223935eb42 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 13 Sep 2023 18:11:28 +0300 Subject: [PATCH 16/17] Small refactorings --- usvm-core/src/main/kotlin/org/usvm/State.kt | 7 +++- .../org/usvm/{Targets.kt => UTarget.kt} | 2 +- .../TargetsReachedStatesCollector.kt | 2 + .../distances/CallStackDistanceCalculator.kt | 9 ++-- .../statistics/distances/CfgStatisticsImpl.kt | 24 ++++++----- .../distances/InterprocDistanceCalculator.kt | 23 ++++++----- .../org/usvm/machine/JcCallGraphStatistics.kt | 11 ++--- .../main/kotlin/org/usvm/machine/JcMachine.kt | 3 +- .../machine/interpreter/JcExprResolver.kt | 11 +++-- .../usvm/machine/interpreter/JcInterpreter.kt | 33 ++------------- .../JcBinaryOperator.kt | 2 +- .../JcOperatorUtils.kt | 2 +- .../JcUnaryOperator.kt | 2 +- .../kotlin/org/usvm/util/JcMethodUtils.kt | 41 ++++++++++++++----- .../JcBinaryOperatorTest.kt | 2 +- .../JcOperatorTestData.kt | 2 +- .../JcUnaryOperatorTest.kt | 2 +- 17 files changed, 89 insertions(+), 89 deletions(-) rename usvm-core/src/main/kotlin/org/usvm/{Targets.kt => UTarget.kt} (96%) rename usvm-jvm/src/main/kotlin/org/usvm/machine/{operators => operator}/JcBinaryOperator.kt (99%) rename usvm-jvm/src/main/kotlin/org/usvm/machine/{operators => operator}/JcOperatorUtils.kt (98%) rename usvm-jvm/src/main/kotlin/org/usvm/machine/{operators => operator}/JcUnaryOperator.kt (98%) rename usvm-jvm/src/test/kotlin/org/usvm/machine/{operators => operator}/JcBinaryOperatorTest.kt (99%) rename usvm-jvm/src/test/kotlin/org/usvm/machine/{operators => operator}/JcOperatorTestData.kt (97%) rename usvm-jvm/src/test/kotlin/org/usvm/machine/{operators => operator}/JcUnaryOperatorTest.kt (99%) diff --git a/usvm-core/src/main/kotlin/org/usvm/State.kt b/usvm-core/src/main/kotlin/org/usvm/State.kt index 824eb1680d..93df656690 100644 --- a/usvm-core/src/main/kotlin/org/usvm/State.kt +++ b/usvm-core/src/main/kotlin/org/usvm/State.kt @@ -1,6 +1,7 @@ package org.usvm import io.ksmt.expr.KInterpretedValue +import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toPersistentList import org.usvm.constraints.UPathConstraints import org.usvm.memory.UMemory @@ -78,7 +79,7 @@ abstract class UState( */ abstract val isExceptional: Boolean - protected var targetsImpl = targets.toPersistentList() + protected var targetsImpl: PersistentList = targets.toPersistentList() private set private val reachedTerminalTargetsImpl = mutableSetOf() @@ -103,14 +104,18 @@ abstract class UState( internal fun tryPropagateTarget(target: Target): Boolean { val previousTargetCount = targetsImpl.size targetsImpl = targetsImpl.remove(target) + if (previousTargetCount == targetsImpl.size || !target.isRemoved) { return false } + if (target.isTerminal) { reachedTerminalTargetsImpl.add(target) return true } + targetsImpl = targetsImpl.addAll(target.children) + return true } } diff --git a/usvm-core/src/main/kotlin/org/usvm/Targets.kt b/usvm-core/src/main/kotlin/org/usvm/UTarget.kt similarity index 96% rename from usvm-core/src/main/kotlin/org/usvm/Targets.kt rename to usvm-core/src/main/kotlin/org/usvm/UTarget.kt index e66b7e81e0..ab3a7ef70b 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Targets.kt +++ b/usvm-core/src/main/kotlin/org/usvm/UTarget.kt @@ -46,7 +46,7 @@ abstract class UTarget( */ fun addChild(child: Target): Target { check(!isRemoved) { "Cannot add child to removed target" } - require(child.parent == null) { "Cannot add child target with existing parent" } + check(child.parent == null) { "Cannot add child target with existing parent" } childrenImpl.add(child) @Suppress("UNCHECKED_CAST") child.parent = this as Target diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt index 9dca452637..62f48e8d28 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/collectors/TargetsReachedStatesCollector.kt @@ -10,6 +10,8 @@ class TargetsReachedStatesCollector> : Stat private val mutableCollectedStates = mutableListOf() override val collectedStates: List = mutableCollectedStates + // TODO probably this should be called not only for terminated states + // Also, we should process more carefully clone operation for the states override fun onStateTerminated(state: State, stateReachable: Boolean) { if (state.reachedTerminalTargets.isNotEmpty()) { mutableCollectedStates.add(state) diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt index 425dc16407..66d69127d7 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CallStackDistanceCalculator.kt @@ -59,18 +59,17 @@ class CallStackDistanceCalculator( override fun calculateDistance(currentStatement: Statement, callStack: UCallStack): UInt { var currentMinDistanceToTarget = UInt.MAX_VALUE - val callStackArray = callStack.toTypedArray() // minDistanceToTarget(F) = // min( // min distance from F to target in current frame (if there are any), // min distance from F to return point R of current frame + minDistanceToTarget(point in prev frame where R returns) // ) - for (i in callStackArray.indices) { - val method = callStackArray[i].method + for (i in callStack.indices) { + val method = callStack[i].method val locationInMethod = - if (i < callStackArray.size - 1) { - val returnSite = callStackArray[i + 1].returnSite + if (i < callStack.size - 1) { + val returnSite = callStack[i + 1].returnSite checkNotNull(returnSite) { "Not first call stack frame had null return site" } } else currentStatement diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CfgStatisticsImpl.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CfgStatisticsImpl.kt index f449fad1b8..9fda57dc50 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CfgStatisticsImpl.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/CfgStatisticsImpl.kt @@ -1,10 +1,8 @@ package org.usvm.statistics.distances -import java.util.concurrent.ConcurrentHashMap -import kotlinx.collections.immutable.ImmutableMap -import kotlinx.collections.immutable.toImmutableMap -import org.usvm.statistics.ApplicationGraph import org.usvm.algorithms.findMinDistancesInUnweightedGraph +import org.usvm.statistics.ApplicationGraph +import java.util.concurrent.ConcurrentHashMap /** * Common [CfgStatistics] implementation with thread-safe results caching. @@ -12,15 +10,17 @@ import org.usvm.algorithms.findMinDistancesInUnweightedGraph * @param applicationGraph [ApplicationGraph] instance to get CFG from. */ class CfgStatisticsImpl( - private val applicationGraph: ApplicationGraph + private val applicationGraph: ApplicationGraph, ) : CfgStatistics { - private val allToAllShortestDistanceCache = ConcurrentHashMap>>() + private val allToAllShortestDistanceCache = ConcurrentHashMap>>() private val shortestDistanceToExitCache = ConcurrentHashMap>() - private fun getAllShortestCfgDistances(method: Method, stmtFrom: Statement): ImmutableMap { + private fun getAllShortestCfgDistances(method: Method, stmtFrom: Statement): Map { val methodCache = allToAllShortestDistanceCache.computeIfAbsent(method) { ConcurrentHashMap() } - return methodCache.computeIfAbsent(stmtFrom) { findMinDistancesInUnweightedGraph(stmtFrom, applicationGraph::successors, methodCache).toImmutableMap() } + return methodCache.computeIfAbsent(stmtFrom) { + findMinDistancesInUnweightedGraph(stmtFrom, applicationGraph::successors, methodCache) + } } override fun getShortestDistance(method: Method, stmtFrom: Statement, stmtTo: Statement): UInt { @@ -28,10 +28,14 @@ class CfgStatisticsImpl( } override fun getShortestDistanceToExit(method: Method, stmtFrom: Statement): UInt { - return shortestDistanceToExitCache.computeIfAbsent(method) { ConcurrentHashMap() } + return shortestDistanceToExitCache + .computeIfAbsent(method) { ConcurrentHashMap() } .computeIfAbsent(stmtFrom) { val exitPoints = applicationGraph.exitPoints(method).toHashSet() - getAllShortestCfgDistances(method, stmtFrom).filterKeys(exitPoints::contains).minByOrNull { it.value }?.value ?: UInt.MAX_VALUE + getAllShortestCfgDistances(method, stmtFrom) + .filterKeys(exitPoints::contains) + .minByOrNull { it.value } + ?.value ?: UInt.MAX_VALUE } } } diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt index c021e70260..fdb1ee437f 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/distances/InterprocDistanceCalculator.kt @@ -37,7 +37,7 @@ class InterprocDistance(val distance: UInt, reachabilityKind: ReachabilityKind) return other.distance == distance && other.reachabilityKind == reachabilityKind } - override fun hashCode(): Int = (23 * 31 + distance.toInt()) * 31 + reachabilityKind.hashCode() + override fun hashCode(): Int = distance.toInt() * 31 + reachabilityKind.hashCode() } /** @@ -59,7 +59,7 @@ internal class InterprocDistanceCalculator( private val callGraphStatistics: CallGraphStatistics ) : DistanceCalculator { - private val frameDistanceCache = HashMap>() + private val frameDistanceCache = hashMapOf>() private fun calculateFrameDistance(method: Method, statement: Statement): InterprocDistance { val targetLocationMethod = applicationGraph.methodOf(targetLocation) @@ -77,7 +77,9 @@ internal class InterprocDistanceCalculator( var minDistanceToCall = UInt.MAX_VALUE for (statementOfMethod in applicationGraph.statementsOf(method)) { - if (!applicationGraph.callees(statementOfMethod).any()) { + val callees = applicationGraph.callees(statementOfMethod) + + if (!callees.any()) { continue } @@ -86,9 +88,7 @@ internal class InterprocDistanceCalculator( continue } - if (applicationGraph.callees(statementOfMethod).any { - callGraphStatistics.checkReachability(it, targetLocationMethod) - }) { + if (callees.any { callGraphStatistics.checkReachability(it, targetLocationMethod) }) { minDistanceToCall = distanceToCall } } @@ -96,6 +96,7 @@ internal class InterprocDistanceCalculator( if (minDistanceToCall != UInt.MAX_VALUE) { frameDistanceCache.computeIfAbsent(method) { hashMapOf() }[statement] = minDistanceToCall } + return InterprocDistance(minDistanceToCall, ReachabilityKind.UP_STACK) } @@ -105,21 +106,21 @@ internal class InterprocDistanceCalculator( ): InterprocDistance { val lastMethod = callStack.lastMethod() val lastFrameDistance = calculateFrameDistance(lastMethod, currentStatement) + if (!lastFrameDistance.isInfinite) { return lastFrameDistance } - listOf().asReversed() - var statementOnCallStack = callStack.last().returnSite + for (i in callStack.size - 2 downTo 0) { val (methodOnCallStack, returnSite) = callStack[i] checkNotNull(statementOnCallStack) { "Not first call stack frame had null return site" } - fun isTargetReachableFrom(statement: Statement): Boolean = - !calculateFrameDistance(methodOnCallStack, statement).isInfinite + val successors = applicationGraph.successors(statementOnCallStack) + val hashReachableSuccessors = successors.any { !calculateFrameDistance(methodOnCallStack, it).isInfinite } - if (applicationGraph.successors(statementOnCallStack).any(::isTargetReachableFrom)) { + if (hashReachableSuccessors) { val distanceToExit = cfgStatistics.getShortestDistanceToExit(lastMethod, currentStatement) return InterprocDistance(distanceToExit, ReachabilityKind.DOWN_STACK) } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt index bfc4f67e5e..9eca91bf0e 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcCallGraphStatistics.kt @@ -1,15 +1,13 @@ package org.usvm.machine -import org.jacodb.api.JcClassType import org.jacodb.api.JcMethod import org.jacodb.api.JcType -import org.jacodb.api.ext.findMethodOrNull import org.jacodb.api.ext.toType import org.usvm.algorithms.limitedBfsTraversal import org.usvm.statistics.distances.CallGraphStatistics import org.usvm.types.UTypeStream import org.usvm.util.canBeOverridden -import org.usvm.util.isDefinition +import org.usvm.util.findMethod import java.util.concurrent.ConcurrentHashMap /** @@ -49,12 +47,11 @@ class JcCallGraphStatistics( typeStream .filterBySupertype(callee.enclosingClass.toType()) .take(subclassesToTake) - .mapNotNullTo(overrides) { - val override = checkNotNull((it as? JcClassType)?.findMethodOrNull(callee.name, callee.description)?.method) { + .mapTo(overrides) { + val calleeMethod = it.findMethod(callee)?.method + checkNotNull(calleeMethod) { "Cannot find overridden method $callee in type $it" } - // Check that the method was actually overridden - if (override.isDefinition()) override else null } } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt index 405bf83812..e0dff78f9b 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcMachine.kt @@ -68,7 +68,7 @@ class JcMachine( options.targetSearchDepth, applicationGraph, typeSystem.topTypeStream(), - 10 + subclassesToTake = 10 ) } @@ -122,7 +122,6 @@ class JcMachine( return statesCollector.collectedStates } - private fun isStateTerminated(state: JcState): Boolean { return state.callStack.isEmpty() } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt index 07600e9d0c..76c649e1db 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt @@ -94,11 +94,11 @@ import org.usvm.collection.array.length.UArrayLengthLValue import org.usvm.collection.field.UFieldLValue import org.usvm.isTrue import org.usvm.machine.JcContext -import org.usvm.machine.operators.JcBinaryOperator -import org.usvm.machine.operators.JcUnaryOperator -import org.usvm.machine.operators.ensureBvExpr -import org.usvm.machine.operators.mkNarrow -import org.usvm.machine.operators.wideTo32BitsIfNeeded +import org.usvm.machine.operator.JcBinaryOperator +import org.usvm.machine.operator.JcUnaryOperator +import org.usvm.machine.operator.ensureBvExpr +import org.usvm.machine.operator.mkNarrow +import org.usvm.machine.operator.wideTo32BitsIfNeeded import org.usvm.machine.state.JcMethodResult import org.usvm.machine.state.JcState import org.usvm.machine.state.addConcreteMethodCallStmt @@ -106,7 +106,6 @@ import org.usvm.machine.state.addVirtualMethodCallStmt import org.usvm.machine.state.throwExceptionWithoutStackFrameDrop import org.usvm.memory.ULValue import org.usvm.memory.URegisterStackLValue -import org.usvm.util.extractJcRefType import org.usvm.util.write /** diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt index d776d6bfb3..be67f37f79 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt @@ -9,7 +9,6 @@ import org.jacodb.api.JcMethod import org.jacodb.api.JcPrimitiveType import org.jacodb.api.JcRefType import org.jacodb.api.JcType -import org.jacodb.api.JcTypedMethod import org.jacodb.api.cfg.JcArgument import org.jacodb.api.cfg.JcAssignInst import org.jacodb.api.cfg.JcCallInst @@ -29,8 +28,6 @@ import org.jacodb.api.cfg.JcSwitchInst import org.jacodb.api.cfg.JcThis import org.jacodb.api.cfg.JcThrowInst import org.jacodb.api.ext.boolean -import org.jacodb.api.ext.findMethodOrNull -import org.jacodb.api.ext.toType import org.jacodb.api.ext.void import org.usvm.INITIAL_INPUT_ADDRESS import org.usvm.StepResult @@ -42,9 +39,9 @@ import org.usvm.api.allocate import org.usvm.api.targets.JcTarget import org.usvm.api.typeStreamOf import org.usvm.machine.JcApplicationGraph +import org.usvm.machine.JcConcreteMethodCallInst import org.usvm.machine.JcContext import org.usvm.machine.JcMethodApproximationResolver -import org.usvm.machine.JcConcreteMethodCallInst import org.usvm.machine.JcMethodCall import org.usvm.machine.JcMethodCallBaseInst import org.usvm.machine.JcMethodEntrypointInst @@ -64,6 +61,7 @@ import org.usvm.machine.state.throwExceptionWithoutStackFrameDrop import org.usvm.memory.URegisterStackLValue import org.usvm.solver.USatResult import org.usvm.types.first +import org.usvm.util.findMethod import org.usvm.util.write typealias JcStepScope = StepScope @@ -440,7 +438,7 @@ class JcInterpreter( val isExpr = typeConstraints[idx] val block = { state: JcState -> - val concreteMethod = type.findMethod(method.name, method.description) + val concreteMethod = type.findMethod(method) ?: error("Can't find method $method in type ${type.typeName}") val concreteCall = methodCall.toConcreteMethodCall(concreteMethod.method) @@ -459,7 +457,7 @@ class JcInterpreter( } else { val type = scope.calcOnState { memory.typeStreamOf(concreteRef) }.first() - val concreteMethod = type.findMethod(method.name, method.description) + val concreteMethod = type.findMethod(method) ?: error("Can't find method $method in type ${type.typeName}") scope.doWithState { @@ -469,29 +467,6 @@ class JcInterpreter( } } - private fun JcType.findMethod(name: String, desc: String): JcTypedMethod? = when (this) { - is JcClassType -> findClassMethod(name, desc) - // Array types are objects and have methods of java.lang.Object - is JcArrayType -> jcClass.toType().findClassMethod(name, desc) - else -> error("Unexpected type: $this") - } - - private fun JcClassType.findClassMethod(name: String, desc: String): JcTypedMethod? { - val method = findMethodOrNull { it.name == name && it.method.description == desc } - if (method != null) return method - - /** - * Method implementation was not found in current class but class is instantiatable. - * Therefore, method implementation is provided by the super class. - * */ - val superClass = superType - if (superClass != null) { - return superClass.findClassMethod(name, desc) - } - - return null - } - private fun approximateMethod(scope: JcStepScope, methodCall: JcMethodCall): Boolean { val exprResolver = exprResolverWithScope(scope) val methodApproximationResolver = JcMethodApproximationResolver( diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcBinaryOperator.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcBinaryOperator.kt similarity index 99% rename from usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcBinaryOperator.kt rename to usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcBinaryOperator.kt index 36b1c9e9a4..3c184aa944 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcBinaryOperator.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcBinaryOperator.kt @@ -1,4 +1,4 @@ -package org.usvm.machine.operators +package org.usvm.machine.operator import io.ksmt.utils.asExpr import io.ksmt.utils.cast diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcOperatorUtils.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcOperatorUtils.kt similarity index 98% rename from usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcOperatorUtils.kt rename to usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcOperatorUtils.kt index 37f09db150..18d480f65c 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcOperatorUtils.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcOperatorUtils.kt @@ -1,4 +1,4 @@ -package org.usvm.machine.operators +package org.usvm.machine.operator import io.ksmt.sort.KBoolSort import io.ksmt.sort.KBvSort diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcUnaryOperator.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcUnaryOperator.kt similarity index 98% rename from usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcUnaryOperator.kt rename to usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcUnaryOperator.kt index 313e2b99c6..796be91c32 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/operators/JcUnaryOperator.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/operator/JcUnaryOperator.kt @@ -1,4 +1,4 @@ -package org.usvm.machine.operators +package org.usvm.machine.operator import io.ksmt.expr.KExpr import io.ksmt.utils.cast diff --git a/usvm-jvm/src/main/kotlin/org/usvm/util/JcMethodUtils.kt b/usvm-jvm/src/main/kotlin/org/usvm/util/JcMethodUtils.kt index f0a0da1d1c..51dfeaba81 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/util/JcMethodUtils.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/util/JcMethodUtils.kt @@ -1,17 +1,12 @@ package org.usvm.util +import org.jacodb.api.JcArrayType +import org.jacodb.api.JcClassType import org.jacodb.api.JcMethod - -/** - * Checks if the method is the same as its definition (i.e. it is false for - * subclasses' methods which use base definition). - */ -fun JcMethod.isDefinition(): Boolean { - if (instList.size == 0) { - return true - } - return instList.first().location.method == this -} +import org.jacodb.api.JcType +import org.jacodb.api.JcTypedMethod +import org.jacodb.api.ext.findMethodOrNull +import org.jacodb.api.ext.toType /** * Checks if the method can be overridden: @@ -26,3 +21,27 @@ fun JcMethod.canBeOverridden(): Boolean = https://stackoverflow.com/a/30416883 */ !isStatic && !isConstructor && !isFinal && !isPrivate && !enclosingClass.isFinal + + +fun JcType.findMethod(method: JcMethod): JcTypedMethod? = when (this) { + is JcClassType -> findClassMethod(method.name, method.description) + // Array types are objects and have methods of java.lang.Object + is JcArrayType -> jcClass.toType().findClassMethod(method.name, method.description) + else -> error("Unexpected type: $this") +} + +private fun JcClassType.findClassMethod(name: String, desc: String): JcTypedMethod? { + val method = findMethodOrNull { it.name == name && it.method.description == desc } + if (method != null) return method + + /** + * Method implementation was not found in current class but class is instantiatable. + * Therefore, method implementation is provided by the super class. + * */ + val superClass = superType + if (superClass != null) { + return superClass.findClassMethod(name, desc) + } + + return null +} diff --git a/usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcBinaryOperatorTest.kt b/usvm-jvm/src/test/kotlin/org/usvm/machine/operator/JcBinaryOperatorTest.kt similarity index 99% rename from usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcBinaryOperatorTest.kt rename to usvm-jvm/src/test/kotlin/org/usvm/machine/operator/JcBinaryOperatorTest.kt index c5f185c01e..91e276bcf4 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcBinaryOperatorTest.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/machine/operator/JcBinaryOperatorTest.kt @@ -1,4 +1,4 @@ -package org.usvm.machine.operators +package org.usvm.machine.operator import io.mockk.mockk import org.junit.jupiter.api.BeforeEach diff --git a/usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcOperatorTestData.kt b/usvm-jvm/src/test/kotlin/org/usvm/machine/operator/JcOperatorTestData.kt similarity index 97% rename from usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcOperatorTestData.kt rename to usvm-jvm/src/test/kotlin/org/usvm/machine/operator/JcOperatorTestData.kt index 4af367aaab..97fa24cb16 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcOperatorTestData.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/machine/operator/JcOperatorTestData.kt @@ -1,4 +1,4 @@ -package org.usvm.machine.operators +package org.usvm.machine.operator val byteData = listOf( 0.toByte(), diff --git a/usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcUnaryOperatorTest.kt b/usvm-jvm/src/test/kotlin/org/usvm/machine/operator/JcUnaryOperatorTest.kt similarity index 99% rename from usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcUnaryOperatorTest.kt rename to usvm-jvm/src/test/kotlin/org/usvm/machine/operator/JcUnaryOperatorTest.kt index 5b63652abf..60909a8ed1 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/machine/operators/JcUnaryOperatorTest.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/machine/operator/JcUnaryOperatorTest.kt @@ -1,4 +1,4 @@ -package org.usvm.machine.operators +package org.usvm.machine.operator import io.mockk.mockk import org.junit.jupiter.api.BeforeEach From a21f111aa8f84b1514320e1b9c9ed5bbc4f5884b Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 13 Sep 2023 18:19:25 +0300 Subject: [PATCH 17/17] Review fixes --- .../kotlin/org/usvm/machine/state/JcState.kt | 1 - .../machine/JcCallGraphStatisticsTests.kt | 22 +++++++++---------- .../src/test/kotlin/org/usvm/util/Util.kt | 2 +- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt index 1a7fe87820..e947887632 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt @@ -19,7 +19,6 @@ class JcState( memory: UMemory = UMemory(ctx, pathConstraints.typeConstraints), models: List> = listOf(), override var pathLocation: PathsTrieNode = ctx.mkInitialLocation(), - // TODO: should set be public? var methodResult: JcMethodResult = JcMethodResult.NoCall, targets: List = emptyList() ) : UState( diff --git a/usvm-jvm/src/test/kotlin/org/usvm/machine/JcCallGraphStatisticsTests.kt b/usvm-jvm/src/test/kotlin/org/usvm/machine/JcCallGraphStatisticsTests.kt index 9e362e4d04..df0efd441a 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/machine/JcCallGraphStatisticsTests.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/machine/JcCallGraphStatisticsTests.kt @@ -6,7 +6,7 @@ import org.usvm.samples.callgraph.CallGraphTestClass1 import org.usvm.samples.callgraph.CallGraphTestClass2 import org.usvm.samples.callgraph.CallGraphTestClass3 import org.usvm.samples.callgraph.CallGraphTestClass4 -import org.usvm.util.getJcMethod +import org.usvm.util.getJcMethodByName import kotlin.test.assertTrue class JcCallGraphStatisticsTests : JavaMethodTestRunner() { @@ -17,37 +17,37 @@ class JcCallGraphStatisticsTests : JavaMethodTestRunner() { @Test fun `base method is reachable`() { - val methodFrom = cp.getJcMethod(CallGraphTestClass3::C) - val methodTo = cp.getJcMethod(CallGraphTestClass1::A) + val methodFrom = cp.getJcMethodByName(CallGraphTestClass3::C) + val methodTo = cp.getJcMethodByName(CallGraphTestClass1::A) assertTrue { statistics.checkReachability(methodFrom, methodTo) } } @Test fun `method override is reachable`() { - val methodFrom = cp.getJcMethod(CallGraphTestClass3::C) - val methodTo = cp.getJcMethod(CallGraphTestClass2::A) + val methodFrom = cp.getJcMethodByName(CallGraphTestClass3::C) + val methodTo = cp.getJcMethodByName(CallGraphTestClass2::A) assertTrue { statistics.checkReachability(methodFrom, methodTo) } } @Test fun `interface implementation is reachable`() { - val methodFrom = cp.getJcMethod(CallGraphTestClass3::D) - val methodTo = cp.getJcMethod(CallGraphTestClass4::A) + val methodFrom = cp.getJcMethodByName(CallGraphTestClass3::D) + val methodTo = cp.getJcMethodByName(CallGraphTestClass4::A) assertTrue { statistics.checkReachability(methodFrom, methodTo) } } @Test fun `final method is reachable`() { - val methodFrom = cp.getJcMethod(CallGraphTestClass3::E) - val methodTo = cp.getJcMethod(CallGraphTestClass1::B) + val methodFrom = cp.getJcMethodByName(CallGraphTestClass3::E) + val methodTo = cp.getJcMethodByName(CallGraphTestClass1::B) assertTrue { statistics.checkReachability(methodFrom, methodTo) } } // CallGraphTestClass3::C -> CallGraphTestClass2::A -> CallGraphTestClass4::A @Test fun `transitive reachability test`() { - val methodFrom = cp.getJcMethod(CallGraphTestClass3::C) - val methodTo = cp.getJcMethod(CallGraphTestClass4::A) + val methodFrom = cp.getJcMethodByName(CallGraphTestClass3::C) + val methodTo = cp.getJcMethodByName(CallGraphTestClass4::A) assertTrue { statistics.checkReachability(methodFrom, methodTo) } } } diff --git a/usvm-jvm/src/test/kotlin/org/usvm/util/Util.kt b/usvm-jvm/src/test/kotlin/org/usvm/util/Util.kt index 4245e57dcb..c48250ad9c 100644 --- a/usvm-jvm/src/test/kotlin/org/usvm/util/Util.kt +++ b/usvm-jvm/src/test/kotlin/org/usvm/util/Util.kt @@ -20,7 +20,7 @@ private val classpath: List .toList() } -fun JcClasspath.getJcMethod(func: KFunction<*>): JcMethod { +fun JcClasspath.getJcMethodByName(func: KFunction<*>): JcMethod { val declaringClassName = requireNotNull(func.javaMethod?.declaringClass?.name) val jcClass = findClass(declaringClassName).toType() return jcClass.declaredMethods.first { it.name == func.name }.method