Skip to content

Commit

Permalink
Targets API (#52)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Alexey Menshutin <[email protected]>
  • Loading branch information
mxprshn and CaelmBleidd authored Sep 13, 2023
1 parent 667fa82 commit 956cdd3
Show file tree
Hide file tree
Showing 71 changed files with 2,009 additions and 487 deletions.
2 changes: 1 addition & 1 deletion usvm-core/src/main/kotlin/org/usvm/CallStack.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ data class UStackTraceFrame<Method, Statement>(

class UCallStack<Method, Statement> private constructor(
private val stack: ArrayDeque<UCallStackFrame<Method, Statement>>,
) : Collection<UCallStackFrame<Method, Statement>> by stack {
) : List<UCallStackFrame<Method, Statement>> by stack {
constructor() : this(ArrayDeque())
constructor(method: Method) : this(
ArrayDeque<UCallStackFrame<Method, Statement>>().apply {
Expand Down
2 changes: 1 addition & 1 deletion usvm-core/src/main/kotlin/org/usvm/Context.kt
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ open class UContext(
// Type hack to be able to intern the initial location for inheritors.
private val initialLocation = RootNode<Nothing, Nothing>()

fun <State : UState<*, *, Statement, *, State>, Statement> mkInitialLocation()
fun <State : UState<*, *, Statement, *, *, State>, Statement> mkInitialLocation()
: PathsTrieNode<State, Statement> = initialLocation.uncheckedCast()

fun mkUValueSampler(): KSortVisitor<KExpr<*>> {
Expand Down
5 changes: 2 additions & 3 deletions usvm-core/src/main/kotlin/org/usvm/Machine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<State> : AutoCloseable {
/**
* Runs symbolic execution loop.
Expand Down
2 changes: 1 addition & 1 deletion usvm-core/src/main/kotlin/org/usvm/Merging.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface UMerger<Entity> {
fun merge(left: Entity, right: Entity): Entity?
}

open class UStateMerger<State : UState<*, *, *, *, State>> : UMerger<State> {
open class UStateMerger<State : UState<*, *, *, *, *, State>> : UMerger<State> {
// Never merge for now
override fun merge(left: State, right: State) = null
}
6 changes: 3 additions & 3 deletions usvm-core/src/main/kotlin/org/usvm/PathTrieNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package org.usvm
/**
* Symbolic execution tree node.
*/
sealed class PathsTrieNode<State : UState<*, *, Statement, *, State>, Statement> {
sealed class PathsTrieNode<State : UState<*, *, Statement, *, *, State>, Statement> {
/**
* Forked states' nodes.
*/
Expand Down Expand Up @@ -65,7 +65,7 @@ sealed class PathsTrieNode<State : UState<*, *, Statement, *, State>, Statement>
}
}

class PathsTrieNodeImpl<State : UState<*, *, Statement, *, State>, Statement> private constructor(
class PathsTrieNodeImpl<State : UState<*, *, Statement, *, *, State>, Statement> private constructor(
override val depth: Int,
override val states: MutableSet<State>,
// Note: order is important for tests
Expand Down Expand Up @@ -101,7 +101,7 @@ class PathsTrieNodeImpl<State : UState<*, *, Statement, *, State>, Statement> pr
override fun toString(): String = "Depth: $depth, statement: $statement"
}

class RootNode<State : UState<*, *, Statement, *, State>, Statement> : PathsTrieNode<State, Statement>() {
class RootNode<State : UState<*, *, Statement, *, *, State>, Statement> : PathsTrieNode<State, Statement>() {
override val children: MutableMap<Statement, PathsTrieNodeImpl<State, Statement>> = mutableMapOf()

override val states: MutableSet<State> = hashSetOf()
Expand Down
57 changes: 51 additions & 6 deletions usvm-core/src/main/kotlin/org/usvm/State.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
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
import org.usvm.model.UModelBase
Expand All @@ -10,15 +12,18 @@ import org.usvm.solver.UUnsatResult

typealias StateId = UInt

abstract class UState<Type, Method, Statement, Context : UContext, State : UState<Type, Method, Statement, Context, State>>(
abstract class UState<Type, Method, Statement, Context, Target, State>(
// TODO: add interpreter-specific information
ctx: UContext,
open val callStack: UCallStack<Method, Statement>,
open val pathConstraints: UPathConstraints<Type, Context>,
open val memory: UMemory<Type, Method>,
open var models: List<UModelBase<Type>>,
open var pathLocation: PathsTrieNode<State, Statement>,
) {
targets: List<Target> = emptyList(),
) where Context : UContext,
Target : UTarget<Statement, Target, State>,
State : UState<Type, Method, Statement, Context, Target, State> {
/**
* Deterministic state id.
* TODO: Can be replaced with overridden hashCode
Expand Down Expand Up @@ -54,7 +59,7 @@ abstract class UState<Type, Method, Statement, Context : UContext, State : UStat
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as UState<*, *, *, *, *>
other as UState<*, *, *, *, *, *>

return id == other.id
}
Expand All @@ -73,6 +78,46 @@ abstract class UState<Type, Method, Statement, Context : UContext, State : UStat
* A property containing information about whether the state is exceptional or not.
*/
abstract val isExceptional: Boolean

protected var targetsImpl: PersistentList<Target> = targets.toPersistentList()
private set

private val reachedTerminalTargetsImpl = mutableSetOf<Target>()

/**
* Collection of state's current targets.
* TODO: clean removed targets sometimes
*/
val targets: Sequence<Target> get() = targetsImpl.asSequence().filterNot { it.isRemoved }

/**
* Reached targets with no children.
*/
val reachedTerminalTargets: Set<Target> = reachedTerminalTargetsImpl

/**
* 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 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
}
}

data class ForkResult<T>(
Expand All @@ -96,7 +141,7 @@ private const val OriginalState = false
* forked state.
*
*/
private fun <T : UState<Type, *, *, Context, T>, Type, Context : UContext> forkIfSat(
private fun <T : UState<Type, *, *, Context, *, T>, Type, Context : UContext> forkIfSat(
state: T,
newConstraintToOriginalState: UBoolExpr,
newConstraintToForkedState: UBoolExpr,
Expand Down Expand Up @@ -156,7 +201,7 @@ private fun <T : UState<Type, *, *, Context, T>, 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 <T : UState<Type, *, *, Context, T>, Type, Context : UContext> fork(
fun <T : UState<Type, *, *, Context, *, T>, Type, Context : UContext> fork(
state: T,
condition: UBoolExpr,
): ForkResult<T> {
Expand Down Expand Up @@ -217,7 +262,7 @@ fun <T : UState<Type, *, *, Context, T>, Type, Context : UContext> fork(
* @return a list of states for each condition - `null` state
* means [UUnknownResult] or [UUnsatResult] of checking condition.
*/
fun <T : UState<Type, *, *, Context, T>, Type, Context : UContext> forkMulti(
fun <T : UState<Type, *, *, Context, *, T>, Type, Context : UContext> forkMulti(
state: T,
conditions: Iterable<UBoolExpr>,
): List<T?> {
Expand Down
2 changes: 1 addition & 1 deletion usvm-core/src/main/kotlin/org/usvm/StepScope.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import org.usvm.StepScope.StepScopeState.DEAD
*
* @param originalState an initial state.
*/
class StepScope<T : UState<Type, *, *, Context, T>, Type, Context : UContext>(
class StepScope<T : UState<Type, *, *, Context, *, T>, Type, Context : UContext>(
private val originalState: T,
) {
private val forkedStates = mutableListOf<T>()
Expand Down
79 changes: 79 additions & 0 deletions usvm-core/src/main/kotlin/org/usvm/UTarget.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.usvm

/**
* 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<Statement, Target, State>(
/**
* Optional location of the target.
*/
val location: Statement? = null,
) where Target : UTarget<Statement, Target, State>,
State : UState<*, *, Statement, *, Target, State> {
private val childrenImpl = mutableListOf<Target>()
private var parent: Target? = null

/**
* List of the child targets which should be reached after this target.
*/
val children: List<Target> = childrenImpl

/**
* True if this target has no children.
*/
val isTerminal get() = childrenImpl.isEmpty()

/**
* True if this target is logically removed from the tree.
*/
var isRemoved = false
private set

/**
* 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" }
check(child.parent == null) { "Cannot add child target with existing parent" }
childrenImpl.add(child)
@Suppress("UNCHECKED_CAST")
child.parent = this as Target
return child
}

/**
* This method should be called by concrete targets to signal that [byState]
* should try to propagate the target. If the target without children has been
* visited, it is logically removed from tree.
*/
protected fun propagate(byState: State) {
@Suppress("UNCHECKED_CAST")
if (byState.tryPropagateTarget(this as Target) && isTerminal) {
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()
}
}
}
4 changes: 2 additions & 2 deletions usvm-core/src/main/kotlin/org/usvm/api/EngineApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
6 changes: 3 additions & 3 deletions usvm-core/src/main/kotlin/org/usvm/api/MockApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import org.usvm.uctx

// TODO: special mock api for variables

fun <Method, T : USort> UState<*, Method, *, *, *>.makeSymbolicPrimitive(
fun <Method, T : USort> UState<*, Method, *, *, *, *>.makeSymbolicPrimitive(
sort: T
): UExpr<T> {
check(sort != sort.uctx.addressSort) { "$sort is not primitive" }
return memory.mock { call(lastEnteredMethod, emptySequence(), sort) }
}

fun <Type, Method> UState<Type, Method, *, *, *>.makeSymbolicRef(type: Type): UHeapRef {
fun <Type, Method> UState<Type, Method, *, *, *, *>.makeSymbolicRef(type: Type): UHeapRef {
val ref = memory.mock { call(lastEnteredMethod, emptySequence(), memory.ctx.addressSort) }

memory.types.addSubtype(ref, type)
Expand All @@ -25,7 +25,7 @@ fun <Type, Method> UState<Type, Method, *, *, *>.makeSymbolicRef(type: Type): UH
return ref
}

fun <Type, Method> UState<Type, Method, *, *, *>.makeSymbolicArray(arrayType: Type, size: USizeExpr): UHeapRef {
fun <Type, Method> UState<Type, Method, *, *, *, *>.makeSymbolicArray(arrayType: Type, size: USizeExpr): UHeapRef {
val ref = memory.mock { call(lastEnteredMethod, emptySequence(), memory.ctx.addressSort) }

memory.types.addSubtype(ref, arrayType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import org.usvm.memory.map
import org.usvm.uctx

object ListCollectionApi {
fun <ListType> UState<ListType, *, *, *, *>.mkSymbolicList(
fun <ListType> UState<ListType, *, *, *, *, *>.mkSymbolicList(
listType: ListType,
): UHeapRef = with(memory.ctx) {
val ref = memory.alloc(listType)
Expand All @@ -27,12 +27,12 @@ object ListCollectionApi {
* List size may be incorrect for input lists.
* Use [ensureListSizeCorrect] to guarantee that list size is correct.
* */
fun <ListType> UState<ListType, *, *, *, *>.symbolicListSize(
fun <ListType> UState<ListType, *, *, *, *, *>.symbolicListSize(
listRef: UHeapRef,
listType: ListType,
): USizeExpr = memory.readArrayLength(listRef, listType)

fun <ListType, State : UState<ListType, *, *, *, State>> StepScope<State, ListType, *>.ensureListSizeCorrect(
fun <ListType, State : UState<ListType, *, *, *, *, State>> StepScope<State, ListType, *>.ensureListSizeCorrect(
listRef: UHeapRef,
listType: ListType,
): Unit? {
Expand All @@ -54,14 +54,14 @@ object ListCollectionApi {
return Unit
}

fun <ListType, Sort : USort> UState<ListType, *, *, *, *>.symbolicListGet(
fun <ListType, Sort : USort> UState<ListType, *, *, *, *, *>.symbolicListGet(
listRef: UHeapRef,
index: USizeExpr,
listType: ListType,
sort: Sort,
): UExpr<Sort> = memory.readArrayIndex(listRef, index, listType, sort)

fun <ListType, Sort : USort> UState<ListType, *, *, *, *>.symbolicListAdd(
fun <ListType, Sort : USort> UState<ListType, *, *, *, *, *>.symbolicListAdd(
listRef: UHeapRef,
listType: ListType,
sort: Sort,
Expand All @@ -76,7 +76,7 @@ object ListCollectionApi {
}
}

fun <ListType, Sort : USort> UState<ListType, *, *, *, *>.symbolicListSet(
fun <ListType, Sort : USort> UState<ListType, *, *, *, *, *>.symbolicListSet(
listRef: UHeapRef,
listType: ListType,
sort: Sort,
Expand All @@ -86,7 +86,7 @@ object ListCollectionApi {
memory.writeArrayIndex(listRef, index, listType, sort, value, guard = memory.ctx.trueExpr)
}

fun <ListType, Sort : USort> UState<ListType, *, *, *, *>.symbolicListInsert(
fun <ListType, Sort : USort> UState<ListType, *, *, *, *, *>.symbolicListInsert(
listRef: UHeapRef,
listType: ListType,
sort: Sort,
Expand Down Expand Up @@ -116,7 +116,7 @@ object ListCollectionApi {
memory.writeArrayLength(listRef, updatedSize, listType)
}

fun <ListType, Sort : USort> UState<ListType, *, *, *, *>.symbolicListRemove(
fun <ListType, Sort : USort> UState<ListType, *, *, *, *, *>.symbolicListRemove(
listRef: UHeapRef,
listType: ListType,
sort: Sort,
Expand All @@ -142,7 +142,7 @@ object ListCollectionApi {
memory.writeArrayLength(listRef, updatedSize, listType)
}

fun <ListType, Sort : USort> UState<ListType, *, *, *, *>.symbolicListCopyRange(
fun <ListType, Sort : USort> UState<ListType, *, *, *, *, *>.symbolicListCopyRange(
srcRef: UHeapRef,
dstRef: UHeapRef,
listType: ListType,
Expand Down
Loading

0 comments on commit 956cdd3

Please sign in to comment.