Skip to content

Commit

Permalink
Track state fork points (#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
Saloed authored Feb 20, 2024
1 parent 1d00c9b commit f9e3883
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 28 deletions.
8 changes: 2 additions & 6 deletions usvm-core/src/main/kotlin/org/usvm/State.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
package org.usvm

import io.ksmt.expr.KInterpretedValue
import org.usvm.constraints.UPathConstraints
import org.usvm.memory.UMemory
import org.usvm.merging.UMergeable
import org.usvm.model.UModelBase
import org.usvm.solver.USatResult
import org.usvm.solver.USolverResult
import org.usvm.solver.UUnknownResult
import org.usvm.solver.UUnsatResult
import org.usvm.targets.UTargetsSet
import org.usvm.targets.UTarget
import org.usvm.targets.UTargetsSet

typealias StateId = UInt

Expand All @@ -26,6 +21,7 @@ abstract class UState<Type, Method, Statement, Context, Target, State>(
*/
open var models: List<UModelBase<Type>>,
open var pathNode: PathNode<Statement>,
open var forkPoints: PathNode<PathNode<Statement>>,
open val targets: UTargetsSet<Target, Statement>,
) : UMergeable<State, Unit>
where Context : UContext<*>,
Expand Down
52 changes: 46 additions & 6 deletions usvm-core/src/main/kotlin/org/usvm/StepScope.kt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class StepScope<T : UState<Type, *, Statement, Context, *, T>, Type, Statement,
): Unit? {
check(canProcessFurtherOnCurrentStep)

val possibleForkPoint = originalState.pathNode

val (posState, negState) = ctx.statesForkProvider.fork(originalState, condition)

posState?.blockOnTrueState()
Expand All @@ -93,6 +95,9 @@ class StepScope<T : UState<Type, *, Statement, Context, *, T>, Type, Statement,
negState.blockOnFalseState()
if (negState !== originalState) {
forkedStates += negState

originalState.forkPoints += possibleForkPoint
negState.forkPoints += possibleForkPoint
}
}

Expand All @@ -106,9 +111,20 @@ class StepScope<T : UState<Type, *, Statement, Context, *, T>, Type, Statement,
*
* NOTE: always sets the [stepScopeState] to the [CANNOT_BE_PROCESSED] value.
*/
fun forkMulti(conditionsWithBlockOnStates: List<Pair<UBoolExpr, T.() -> Unit>>) {
fun forkMulti(conditionsWithBlockOnStates: List<Pair<UBoolExpr, T.() -> Unit>>) =
forkMulti(conditionsWithBlockOnStates, skipForkPointIfPossible = true)

/**
* @param skipForkPointIfPossible determines whether it is allowed to skip fork point registration.
* */
private fun forkMulti(
conditionsWithBlockOnStates: List<Pair<UBoolExpr, T.() -> Unit>>,
skipForkPointIfPossible: Boolean
) {
check(canProcessFurtherOnCurrentStep)

val possibleForkPoint = originalState.pathNode

val conditions = conditionsWithBlockOnStates.map { it.first }

val conditionStates = ctx.statesForkProvider.forkMulti(originalState, conditions)
Expand All @@ -132,18 +148,35 @@ class StepScope<T : UState<Type, *, Statement, Context, *, T>, Type, Statement,

// Interpret the first state as original and others as forked
this.forkedStates += forkedStates.subList(1, forkedStates.size)

if (skipForkPointIfPossible && forkedStates.size < 2) return

forkedStates.forEach { it.forkPoints += possibleForkPoint }
}

fun assert(
fun assert(constraint: UBoolExpr, block: T.() -> Unit = {}): Unit? =
assert(constraint, registerForkPoint = false, block)

/**
* @param registerForkPoint register a fork point if assert was successful.
* */
private fun assert(
constraint: UBoolExpr,
block: T.() -> Unit = {},
registerForkPoint: Boolean,
block: T.() -> Unit,
): Unit? {
check(canProcessFurtherOnCurrentStep)

val possibleForkPoint = originalState.pathNode

val (posState) = ctx.statesForkProvider.forkMulti(originalState, listOf(constraint))

posState?.block()

if (registerForkPoint && posState != null) {
posState.forkPoints += possibleForkPoint
}

if (posState == null) {
stepScopeState = DEAD
}
Expand Down Expand Up @@ -180,12 +213,15 @@ class StepScope<T : UState<Type, *, Statement, Context, *, T>, Type, Statement,
return fork(condition, blockOnTrueState, blockOnFalseState)
}

// If condition is concrete there is no fork point possibility
val registerForkPoint = !condition.isConcrete

// TODO: asserts are implemented via forkMulti and create an unused copy of state
if (shouldForkOnTrue) {
return assert(condition, blockOnTrueState)
return assert(condition, registerForkPoint, blockOnTrueState)
}

return assert(condition.uctx.mkNot(condition), blockOnFalseState)
return assert(condition.uctx.mkNot(condition), registerForkPoint, blockOnFalseState)
}

/**
Expand All @@ -208,9 +244,13 @@ class StepScope<T : UState<Type, *, Statement, Context, *, T>, Type, Statement,
return
}

return forkMulti(filteredConditionsWithBlockOnStates)
// If all conditions are concrete there is no fork point possibility
val skipForkPoint = forkCases.all { it.condition.isConcrete }
return forkMulti(filteredConditionsWithBlockOnStates, skipForkPoint)
}

private val UBoolExpr.isConcrete get() = isTrue || isFalse

/**
* [assert]s the [condition] on the scope with the cloned [originalState]. Returns this cloned state,
* if this [condition] is satisfiable, and returns `null` otherwise.
Expand Down
4 changes: 2 additions & 2 deletions usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -303,13 +303,13 @@ private fun <Method, Statement, State : UState<*, Method, Statement, *, *, State
if (random == null) {
return WeightedPathSelector(
priorityCollectionFactory = { DeterministicPriorityCollection(Comparator.naturalOrder()) },
weighter = { it.pathNode.depth }
weighter = { it.forkPoints.depth }
)
}

return WeightedPathSelector(
priorityCollectionFactory = { RandomizedPriorityCollection(compareById()) { random.nextDouble() } },
weighter = { 1.0 / max(it.pathNode.depth.toDouble(), 1.0) }
weighter = { 1.0 / max(it.forkPoints.depth.toDouble(), 1.0) }
)
}

Expand Down
2 changes: 1 addition & 1 deletion usvm-core/src/test/kotlin/org/usvm/TestUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ internal class TestState(
pathLocation: PathNode<TestInstruction>,
targetTrees: UTargetsSet<TestTarget, TestInstruction> = UTargetsSet.empty(),
override val entrypoint: TestMethod = ""
) : UState<Any, TestMethod, TestInstruction, UContext<*>, TestTarget, TestState>(ctx, callStack, pathConstraints, memory, models, pathLocation, targetTrees) {
) : UState<Any, TestMethod, TestInstruction, UContext<*>, TestTarget, TestState>(ctx, callStack, pathConstraints, memory, models, pathLocation, PathNode.root(), targetTrees) {

override fun clone(newConstraints: UPathConstraints<Any>?): TestState = this

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ abstract class SymbolicCollectionTestBase {
memory: UMemory<SingleTypeSystem.SingleType, Any?>,
) : UState<SingleTypeSystem.SingleType, Any?, Any?, UContext<USizeSort>, TargetStub, StateStub>(
ctx, UCallStack(),
pathConstraints, memory, emptyList(), PathNode.root(), UTargetsSet.empty()
pathConstraints, memory, emptyList(), PathNode.root(), PathNode.root(), UTargetsSet.empty()
) {
override fun clone(newConstraints: UPathConstraints<SingleTypeSystem.SingleType>?): StateStub {
val clonedConstraints = newConstraints ?: pathConstraints.clone()
Expand Down
5 changes: 5 additions & 0 deletions usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class JcState(
memory: UMemory<JcType, JcMethod> = UMemory(ctx, pathConstraints.typeConstraints),
models: List<UModelBase<JcType>> = listOf(),
pathNode: PathNode<JcInst> = PathNode.root(),
forkPoints: PathNode<PathNode<JcInst>> = PathNode.root(),
var methodResult: JcMethodResult = JcMethodResult.NoCall,
targets: UTargetsSet<JcTarget, JcInst> = UTargetsSet.empty(),
) : UState<JcType, JcMethod, JcInst, JcContext, JcTarget, JcState>(
Expand All @@ -31,6 +32,7 @@ class JcState(
memory,
models,
pathNode,
forkPoints,
targets
) {
override fun clone(newConstraints: UPathConstraints<JcType>?): JcState {
Expand All @@ -43,6 +45,7 @@ class JcState(
memory.clone(clonedConstraints.typeConstraints),
models,
pathNode,
forkPoints,
methodResult,
targets.clone(),
)
Expand All @@ -58,6 +61,7 @@ class JcState(
// TODO: copy-paste

val mergedPathNode = pathNode.mergeWith(other.pathNode, Unit) ?: return null
val mergedForkPoints = forkPoints.mergeWith(other.forkPoints, Unit) ?: return null

val mergeGuard = MutableMergeGuard(ctx)
val mergedCallStack = callStack.mergeWith(other.callStack, Unit) ?: return null
Expand All @@ -82,6 +86,7 @@ class JcState(
mergedMemory,
mergedModels,
mergedPathNode,
mergedForkPoints,
methodResult,
mergedTargets
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@ package org.usvm.samples.wrappers

import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.usvm.PathSelectionStrategy
import org.usvm.samples.JavaMethodTestRunner
import org.usvm.test.util.checkers.eq


internal class LongWrapperTest : JavaMethodTestRunner() {
@Test
fun primitiveToWrapperTest() {
checkDiscoveredProperties(
LongWrapper::primitiveToWrapper,
eq(2),
{ _, x, r -> x >= 0 && r != null && r <= 0 },
{ _, x, r -> x < 0 && r != null && r < 0 },
)
// todo: investigate why only BFS works
val options = options.copy(pathSelectionStrategies = listOf(PathSelectionStrategy.BFS))
withOptions(options) {
checkDiscoveredProperties(
LongWrapper::primitiveToWrapper,
eq(2),
{ _, x, r -> x >= 0 && r != null && r <= 0 },
{ _, x, r -> x < 0 && r != null && r < 0 },
)
}
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@ package org.usvm.samples.wrappers

import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.usvm.PathSelectionStrategy
import org.usvm.samples.JavaMethodTestRunner
import org.usvm.test.util.checkers.eq


internal class ShortWrapperTest : JavaMethodTestRunner() {
@Test
fun primitiveToWrapperTest() {
checkDiscoveredProperties(
ShortWrapper::primitiveToWrapper,
eq(2),
{ _, x, r -> x >= 0 && r != null && r <= 0 },
{ _, x, r -> x < 0 && r != null && r < 0 },
)
// todo: investigate why only BFS works
val options = options.copy(pathSelectionStrategies = listOf(PathSelectionStrategy.BFS))
withOptions(options) {
checkDiscoveredProperties(
ShortWrapper::primitiveToWrapper,
eq(2),
{ _, x, r -> x >= 0 && r != null && r <= 0 },
{ _, x, r -> x < 0 && r != null && r < 0 },
)
}
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class SampleState(
memory: UMemory<SampleType, Method<*>> = UMemory(ctx, pathConstraints.typeConstraints),
models: List<UModelBase<SampleType>> = listOf(),
pathNode: PathNode<Stmt> = PathNode.root(),
forkPoints: PathNode<PathNode<Stmt>> = PathNode.root(),
var returnRegister: UExpr<out USort>? = null,
var exceptionRegister: ProgramException? = null,
targets: UTargetsSet<SampleTarget, Stmt> = UTargetsSet.empty(),
Expand All @@ -36,6 +37,7 @@ class SampleState(
memory,
models,
pathNode,
forkPoints,
targets
) {
override fun clone(newConstraints: UPathConstraints<SampleType>?): SampleState {
Expand All @@ -48,6 +50,7 @@ class SampleState(
memory.clone(clonedConstraints.typeConstraints),
models,
pathNode,
forkPoints,
returnRegister,
exceptionRegister,
targets
Expand All @@ -63,6 +66,7 @@ class SampleState(
require(entrypoint == other.entrypoint) { "Cannot merge states with different entrypoints" }

val mergedPathNode = pathNode.mergeWith(other.pathNode, Unit) ?: return null
val mergedForkPoints = forkPoints.mergeWith(other.forkPoints, Unit) ?: return null

val mergeGuard = MutableMergeGuard(ctx)
val mergedCallStack = callStack.mergeWith(other.callStack, Unit) ?: return null
Expand Down Expand Up @@ -92,6 +96,7 @@ class SampleState(
mergedMemory,
mergedModels,
mergedPathNode,
mergedForkPoints,
mergedReturnRegister,
mergedExceptionRegister,
mergedTargets
Expand Down

0 comments on commit f9e3883

Please sign in to comment.