From 7f4cf059097ab7d8d6294847e19ac2073d53cc71 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Thu, 23 Mar 2023 20:46:41 +0300 Subject: [PATCH 01/28] Draft --- .../src/main/kotlin/org/usvm/Composition.kt | 4 +- .../main/kotlin/org/usvm/ExprTranslator.kt | 135 ++++++++++ .../src/main/kotlin/org/usvm/Expressions.kt | 8 +- usvm-core/src/main/kotlin/org/usvm/Heap.kt | 2 +- .../src/main/kotlin/org/usvm/MemoryRegions.kt | 56 ++-- .../src/main/kotlin/org/usvm/MemoryUpdates.kt | 30 ++- .../src/main/kotlin/org/usvm/RegionIds.kt | 123 +++++---- .../main/kotlin/org/usvm/RegionTranslator.kt | 249 ++++++++++++++++++ .../main/kotlin/org/usvm/UExprTransformer.kt | 2 +- .../src/main/kotlin/org/usvm/UpdateNodes.kt | 6 +- .../test/kotlin/org/usvm/CompositionTest.kt | 16 +- .../kotlin/org/usvm/MapCompositionTest.kt | 6 +- .../test/kotlin/org/usvm/TranslationTest.kt | 158 +++++++++++ 13 files changed, 675 insertions(+), 120 deletions(-) create mode 100644 usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt create mode 100644 usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt create mode 100644 usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/Composition.kt b/usvm-core/src/main/kotlin/org/usvm/Composition.kt index 51ecbff8f6..9cdcf05840 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Composition.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Composition.kt @@ -32,7 +32,7 @@ open class UComposer( typeEvaluator.evalIs(composedAddress, type) } - fun , Key, Sort : USort> transformHeapReading( + fun , Key, Sort : USort> transformHeapReading( expr: UHeapReading, key: Key ): UExpr = with(expr) { @@ -40,7 +40,7 @@ open class UComposer( // Create a copy of this heap to avoid its modification val heapToApplyUpdates = heapEvaluator.toMutableHeap() memoryRegion.applyTo(heapToApplyUpdates) - region.regionId.read(key, sort, heapToApplyUpdates) + region.regionId.read(heapToApplyUpdates, key) } val mappedRegion = region.map(this@UComposer, instantiator) diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt new file mode 100644 index 0000000000..432f103c6e --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -0,0 +1,135 @@ +package org.usvm + +import org.ksmt.utils.mkConst + +open class UExprTranslator internal constructor( + ctx: UContext, +) : UExprTransformer(ctx) { + private val observers = mutableListOf() + + internal fun attachObserver(observer: UTranslationObserver) { + observers += observer + } + + open fun translate(expr: UExpr): UExpr = apply(expr) + + // TODO: why do we have this function in UExprTransformer? + override fun transform(expr: USymbol): UExpr { + error("You must override `transform` function in org.usvm.UExprTranslator for ${expr::class}") + } + + override fun transform(expr: URegisterReading): UExpr { + val registerConst = expr.sort.mkConst("r${expr.idx}") + observers.forEach { it.newRegisterReadingTranslated(expr.idx, registerConst) } + return registerConst + } + + // TODO: why do we have this function in UExprTransformer? + override fun transform(expr: UHeapReading<*, *, *>): UExpr = + error("You must override `transform` function in UExprTranslator for ${expr::class}") + + // TODO: why do we have this function in UExprTransformer? + override fun transform(expr: UMockSymbol): UExpr = + error("You must override `transform` function in UExprTranslator for ${expr::class}") + + override fun transform(expr: UIndexedMethodReturnValue): UExpr { + val const = expr.sort.mkConst("m${expr.method}_${expr.callIndex}") + observers.forEach { it.newIndexedMethodReturnValueTranslated(expr.method, expr.callIndex, const) } + return const + } + + override fun transform(expr: UNullRef): UExpr = expr.sort.mkConst("null") + + override fun transform(expr: UConcreteHeapRef): UExpr { + error("Unexpected UConcreteHeapRef $expr in UExprTranslator, that has to be impossible by construction!") + } + + override fun transform(expr: UIsExpr): UBoolExpr { + error("Unexpected UIsExpr $expr in UExprTranslator, that has to be impossible by construction!") + } + + override fun transform(expr: UInputArrayLengthReading): USizeExpr = + transformExprAfterTransformed(expr, expr.address) { address -> + translateRegionReading(expr.region, address) + } + + override fun transform(expr: UInputArrayReading): UExpr = + transformExprAfterTransformed(expr, expr.address, expr.index) { address, index -> + translateRegionReading(expr.region, address to index) + } + + override fun transform(expr: UAllocatedArrayReading): UExpr = + transformExprAfterTransformed(expr, expr.index) { index -> + translateRegionReading(expr.region, index) + } + + override fun transform(expr: UInputFieldReading): UExpr = + transformExprAfterTransformed(expr, expr.address) { address -> + translateRegionReading(expr.region, address) + } + + private val regionToTranslator = mutableMapOf, URegionTranslator<*, *, *, *>>() + .withDefault { regionId -> + val regionTranslator = regionId.translator(this) + observers.forEach { it.newRegionTranslator(regionId, regionTranslator) } + regionTranslator + } + + open fun translateRegionReading( + region: UMemoryRegion, Key, Sort>, + key: Key, + ): UExpr { + @Suppress("UNCHECKED_CAST") + val translator = + regionToTranslator.getValue(region.regionId) as URegionTranslator, Key, Sort, *> + return translator.translateReading(region, key) + } +} + +internal typealias RegionTranslatorConstructor = (UExprTranslator<*, *>) -> URegionTranslator, T, U, *> + +// TODO: maybe split this function into functions of URegionID +internal val URegionId.translator: RegionTranslatorConstructor + get() = { translator -> + val ctx = sort.uctx + @Suppress("UNCHECKED_CAST") + when (this) { + is UInputArrayId<*, Sort> -> { + val updateTranslator = U2DArrayUpdateTranslator(translator, ctx.addressSort, ctx.sizeSort, this) + val updatesTranslator = UTreeUpdatesTranslator(updateTranslator) + URegionTranslator(updateTranslator, updatesTranslator) + } + is UAllocatedArrayId<*, Sort> -> { + val updateTranslator = U1DArrayUpdateTranslator(translator, ctx.sizeSort, this) + val updatesTranslator = UTreeUpdatesTranslator(updateTranslator) + URegionTranslator(updateTranslator, updatesTranslator) + } + is UInputArrayLengthId<*> -> { + val updateTranslator = U1DArrayUpdateTranslator(translator, ctx.addressSort, this) + val updatesTranslator = UFlatUpdatesTranslator(updateTranslator) + URegionTranslator(updateTranslator, updatesTranslator) + } + is UInputFieldRegionId<*, Sort> -> { + val updateTranslator = U1DArrayUpdateTranslator(translator, ctx.addressSort, this) + val updatesTranslator = UFlatUpdatesTranslator(updateTranslator) + URegionTranslator(updateTranslator, updatesTranslator) + } + else -> error("Unexpected regionId: $this") + } as URegionTranslator, Key, Sort, *> + } + +// TODO: looks odd, because we duplicate StackEvaluator::eval, MockEvaluator::eval with slightly changed signature... +internal interface UTranslationObserver { + fun newRegionTranslator( + regionId: URegionId<*, *>, + translator: URegionTranslator<*, *, *, *>, + ) + + fun newRegisterReadingTranslated(idx: Int, translated: UExpr) + + fun newIndexedMethodReturnValueTranslated( + method: Method, + callIndex: Int, + translated: UExpr, + ) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/Expressions.kt b/usvm-core/src/main/kotlin/org/usvm/Expressions.kt index 82c7ee47f9..1bdd84b269 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Expressions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Expressions.kt @@ -127,7 +127,7 @@ class URegisterReading internal constructor( } } -abstract class UHeapReading, Key, Sort : USort>( +abstract class UHeapReading, Key, Sort : USort>( ctx: UContext, val region: UMemoryRegion ) : USymbol(ctx) { @@ -138,7 +138,7 @@ class UInputFieldReading internal constructor( ctx: UContext, region: UInputFieldRegion, val address: UHeapRef, -) : UHeapReading, UHeapRef, Sort>(ctx, region) { +) : UHeapReading, UHeapRef, Sort>(ctx, region) { @Suppress("UNCHECKED_CAST") override fun accept(transformer: KTransformerBase): KExpr { require(transformer is UExprTransformer<*, *>) @@ -162,7 +162,7 @@ class UAllocatedArrayReading internal constructor( ctx: UContext, region: UAllocatedArrayRegion, val index: USizeExpr, -) : UHeapReading, USizeExpr, Sort>(ctx, region) { +) : UHeapReading, USizeExpr, Sort>(ctx, region) { @Suppress("UNCHECKED_CAST") override fun accept(transformer: KTransformerBase): KExpr { require(transformer is UExprTransformer<*, *>) @@ -192,7 +192,7 @@ class UInputArrayReading internal constructor( region: UInputArrayRegion, val address: UHeapRef, val index: USizeExpr -) : UHeapReading, USymbolicArrayIndex, Sort>(ctx, region) { +) : UHeapReading, USymbolicArrayIndex, Sort>(ctx, region) { @Suppress("UNCHECKED_CAST") override fun accept(transformer: KTransformerBase): KExpr { require(transformer is UExprTransformer<*, *>) diff --git a/usvm-core/src/main/kotlin/org/usvm/Heap.kt b/usvm-core/src/main/kotlin/org/usvm/Heap.kt index aa9aac1c8a..725efcb1f0 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Heap.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Heap.kt @@ -122,7 +122,7 @@ data class URegionHeap( arrayType: ArrayType, ): UInputArrayLengthRegion = inputLengths[arrayType] - ?: emptyArrayLengthRegion(arrayType, ctx) { ref, region -> + ?: emptyArrayLengthRegion(arrayType, ctx.sizeSort) { ref, region -> ctx.mkInputArrayLengthReading(region, ref) } diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt index 0571d139e0..725509eaa6 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt @@ -22,13 +22,14 @@ typealias UInstantiator = (key: Key, UMemoryRegion, Key, Sort : USort>( +data class UMemoryRegion, Key, Sort : USort>( val regionId: RegionId, - val sort: Sort, val updates: UMemoryUpdates, - private val defaultValue: UExpr?, // If defaultValue = null then this region is filled with symbolics private val instantiator: UInstantiator, ) { + val sort: Sort get() = regionId.sort + private val defaultValue = regionId.defaultValue + private fun read(key: Key, updates: UMemoryUpdates): UExpr { val lastUpdatedElement = updates.lastUpdatedElementOrNull() @@ -159,18 +160,20 @@ data class UMemoryRegion, Key, Sort : USort>( composer: UComposer, instantiator: UInstantiator = this.instantiator, ): UMemoryRegion { - // Map the updates and the default value + // Map the updates and the regionId + @Suppress("UNCHECKED_CAST") + val mappedRegionId = regionId.map(composer) as RegionId val mappedUpdates = updates.map(regionId.keyMapper(composer), composer) - val mappedDefaultValue = defaultValue?.let { composer.compose(it) } // Note that we cannot use optimization with unchanged mappedUpdates and mappedDefaultValues here // since in a new region we might have an updated instantiator. // Therefore, we have to check their reference equality as well. - if (mappedUpdates === updates && mappedDefaultValue === defaultValue && instantiator === this.instantiator) { + if (mappedUpdates === updates && mappedRegionId === regionId && instantiator === this.instantiator) { return this } - return UMemoryRegion(regionId, sort, mappedUpdates, mappedDefaultValue, instantiator) + // Otherwise, construct a new region with mapped values and a new instantiator. + return UMemoryRegion(mappedRegionId, mappedUpdates, instantiator) } @Suppress("UNCHECKED_CAST") @@ -178,7 +181,7 @@ data class UMemoryRegion, Key, Sort : USort>( // Apply each update on the copy updates.forEach { when (it) { - is UPinpointUpdateNode -> regionId.write(it.key, sort, heap, it.value, it.guard) + is UPinpointUpdateNode -> regionId.write(heap, it.key, it.value, it.guard) is URangedUpdateNode<*, *, *, Key, Sort> -> { it.region.applyTo(heap) @@ -198,7 +201,7 @@ data class UMemoryRegion, Key, Sort : USort>( * with values from memory region [fromRegion] read from range * of addresses [[keyConverter].convert([fromKey]) : [keyConverter].convert([toKey])] */ - fun , SrcKey> copyRange( + fun , SrcKey> copyRange( fromRegion: UMemoryRegion, fromKey: Key, toKey: Key, keyConverter: UMemoryKeyConverter, @@ -243,7 +246,7 @@ class GuardBuilder(nonMatchingUpdates: UBoolExpr) { typealias USymbolicArrayIndex = Pair fun heapRefEq(ref1: UHeapRef, ref2: UHeapRef): UBoolExpr = - ref1.uctx.mkHeapRefEq(ref1, ref2) // TODO: use simplified equality! + ref1.uctx.mkHeapRefEq(ref1, ref2) @Suppress("UNUSED_PARAMETER") fun heapRefCmpSymbolic(ref1: UHeapRef, ref2: UHeapRef): UBoolExpr = @@ -254,17 +257,16 @@ fun heapRefCmpConcrete(ref1: UHeapRef, ref2: UHeapRef): Boolean = error("Heap references should not be compared!") fun indexEq(idx1: USizeExpr, idx2: USizeExpr): UBoolExpr = - idx1.ctx.mkEq(idx1, idx2) // TODO: use simplified equality! + idx1.ctx.mkEq(idx1, idx2) fun indexLeSymbolic(idx1: USizeExpr, idx2: USizeExpr): UBoolExpr = - idx1.ctx.mkBvSignedLessOrEqualExpr(idx1, idx2) // TODO: use simplified comparison! + idx1.ctx.mkBvSignedLessOrEqualExpr(idx1, idx2) fun indexLeConcrete(idx1: USizeExpr, idx2: USizeExpr): Boolean = // TODO: to optimize things up, we could pass path constraints here and lookup the numeric bounds for idx1 and idx2 idx1 == idx2 || (idx1 is UConcreteSize && idx2 is UConcreteSize && idx1.numberValue <= idx2.numberValue) fun refIndexEq(idx1: USymbolicArrayIndex, idx2: USymbolicArrayIndex): UBoolExpr = with(idx1.first.ctx) { - // TODO: use simplified operations! return@with (idx1.first eq idx2.first) and indexEq(idx1.second, idx2.second) } @@ -301,9 +303,9 @@ fun refIndexRangeRegion( idx2: USymbolicArrayIndex, ): UArrayIndexRegion = indexRangeRegion(idx1.second, idx2.second) -typealias UInputFieldRegion = UMemoryRegion, UHeapRef, Sort> -typealias UAllocatedArrayRegion = UMemoryRegion, USizeExpr, Sort> -typealias UInputArrayRegion = UMemoryRegion, USymbolicArrayIndex, Sort> +typealias UInputFieldRegion = UMemoryRegion, UHeapRef, Sort> +typealias UAllocatedArrayRegion = UMemoryRegion, USizeExpr, Sort> +typealias UInputArrayRegion = UMemoryRegion, USymbolicArrayIndex, Sort> typealias UInputArrayLengthRegion = UMemoryRegion, UHeapRef, USizeSort> typealias KeyMapper = (Key) -> Key @@ -325,12 +327,10 @@ val UInputArrayLengthRegion.inputLengthArrayType fun emptyInputFieldRegion( field: Field, sort: Sort, - instantiator: UInstantiator, UHeapRef, Sort>, + instantiator: UInstantiator, UHeapRef, Sort>, ): UInputFieldRegion = UMemoryRegion( - UInputFieldRegionId(field), - sort, + UInputFieldRegionId(field, sort), UEmptyUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic), - defaultValue = null, instantiator ) @@ -338,38 +338,36 @@ fun emptyAllocatedArrayRegion( arrayType: ArrayType, address: UConcreteHeapAddress, sort: Sort, - instantiator: UInstantiator, USizeExpr, Sort>, + instantiator: UInstantiator, USizeExpr, Sort>, ): UAllocatedArrayRegion { val updates = UTreeUpdates( updates = emptyRegionTree(), ::indexRegion, ::indexRangeRegion, ::indexEq, ::indexLeConcrete, ::indexLeSymbolic ) - val regionId = UAllocatedArrayId(arrayType, address) - return UMemoryRegion(regionId, sort, updates, sort.defaultValue(), instantiator) + val regionId = UAllocatedArrayId(arrayType, address, sort) + return UMemoryRegion(regionId, updates, instantiator) } fun emptyInputArrayRegion( arrayType: ArrayType, sort: Sort, - instantiator: UInstantiator, USymbolicArrayIndex, Sort>, + instantiator: UInstantiator, USymbolicArrayIndex, Sort>, ): UInputArrayRegion { val updates = UTreeUpdates( updates = emptyRegionTree(), ::refIndexRegion, ::refIndexRangeRegion, ::refIndexEq, ::refIndexCmpConcrete, ::refIndexCmpSymbolic ) - return UMemoryRegion(UInputArrayId(arrayType), sort, updates, defaultValue = null, instantiator) + return UMemoryRegion(UInputArrayId(arrayType, sort), updates, instantiator) } fun emptyArrayLengthRegion( arrayType: ArrayType, - ctx: UContext, + sizeSort: USizeSort, instantiator: UInstantiator, UHeapRef, USizeSort>, ): UInputArrayLengthRegion = UMemoryRegion( - UInputArrayLengthId(arrayType), - ctx.sizeSort, + UInputArrayLengthId(arrayType, sizeSort), UEmptyUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic), - defaultValue = null, instantiator ) diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt index 8d89c95205..e182ec50e4 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt @@ -49,7 +49,7 @@ interface UMemoryUpdates : Sequence> { * * @see UMemoryRegion.copyRange */ - fun , SrcKey> copyRange( + fun , SrcKey> copyRange( fromRegion: UMemoryRegion, fromKey: Key, toKey: Key, @@ -90,7 +90,7 @@ class UEmptyUpdates( symbolicCmp ) - override fun , SrcKey> copyRange( + override fun , SrcKey> copyRange( fromRegion: UMemoryRegion, fromKey: Key, toKey: Key, @@ -146,7 +146,7 @@ data class UFlatUpdates( symbolicCmp ) - override fun , SrcKey> copyRange( + override fun , SrcKey> copyRange( fromRegion: UMemoryRegion, fromKey: Key, toKey: Key, @@ -238,7 +238,7 @@ data class UFlatUpdates( //region Tree memory updates data class UTreeUpdates, Sort : USort>( - private val updates: RegionTree, Reg>, + internal val updates: RegionTree, Reg>, private val keyToRegion: (Key) -> Reg, private val keyRangeToRegion: (Key, Key) -> Reg, private val symbolicEq: (Key, Key) -> UBoolExpr, @@ -266,7 +266,7 @@ data class UTreeUpdates, Sort : USort>( return this.copy(updates = newUpdates) } - override fun , SrcKey> copyRange( + override fun , SrcKey> copyRange( fromRegion: UMemoryRegion, fromKey: Key, toKey: Key, @@ -424,14 +424,7 @@ data class UTreeUpdates, Sort : USort>( while (treeUpdatesIterator.hasNext()) { val (update, region) = treeUpdatesIterator.next() - // To check, whether we have a duplicate for a particular key, - // we have to check if an initial region (by USVM estimation) is equal - // to the one stored in the current node. - val initialRegion = when (update) { - is UPinpointUpdateNode -> keyToRegion(update.key) - is URangedUpdateNode<*, *, *, Key, Sort> -> keyRangeToRegion(update.fromKey, update.toKey) - } - val wasCloned = initialRegion != region + val wasCloned = checkWasCloned(update, region) // If a region from the current node is equal to the initial region, // it means that there were no write operation that caused nodes split, @@ -460,6 +453,17 @@ data class UTreeUpdates, Sort : USort>( throw NoSuchElementException() } } + internal fun checkWasCloned(update: UUpdateNode, region: Region<*>): Boolean { + // To check, whether we have a duplicate for a particular key, + // we have to check if an initial region (by USVM estimation) is equal + // to the one stored in the current node. + val initialRegion = when (update) { + is UPinpointUpdateNode -> keyToRegion(update.key) + is URangedUpdateNode<*, *, *, Key, Sort> -> keyRangeToRegion(update.fromKey, update.toKey) + } + val wasCloned = initialRegion != region + return wasCloned + } override fun lastUpdatedElementOrNull(): UUpdateNode? = updates.entries.entries.lastOrNull()?.value?.first diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt index 1b00f6653f..2da665b52f 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt @@ -6,56 +6,63 @@ import org.ksmt.utils.asExpr /** * An interface that represents any possible type of regions that can be used in the memory. */ -interface URegionId { - fun read( - key: Key, - sort: Sort, +interface URegionId { + val sort: Sort + val defaultValue: UExpr? + + fun read( heap: UReadOnlySymbolicHeap, + key: Key, ): UExpr - fun write( - key: Key, - sort: Sort, + fun write( heap: USymbolicHeap, + key: Key, value: UExpr, guard: UBoolExpr, ) - fun keyMapper(composer: UComposer): KeyMapper + fun keyMapper(transformer: UExprTransformer): KeyMapper + + fun map(composer: UComposer): URegionId } /** * A region id for a region storing the specific [field]. */ -data class UInputFieldRegionId internal constructor( +data class UInputFieldRegionId internal constructor( val field: Field, -) : URegionId { + override val sort: Sort, +) : URegionId { + override val defaultValue get() = null + @Suppress("UNCHECKED_CAST") - override fun read( - key: UHeapRef, - sort: Sort, + override fun read( heap: UReadOnlySymbolicHeap, + key: UHeapRef, ) = heap.readField(key, field as Field, sort).asExpr(sort) @Suppress("UNCHECKED_CAST") - override fun write( - key: UHeapRef, - sort: Sort, + override fun write( heap: USymbolicHeap, + key: UHeapRef, value: UExpr, guard: UBoolExpr, ) = heap.writeField(key, field as Field, sort, value, guard) override fun keyMapper( - composer: UComposer, - ): KeyMapper = { composer.compose(it) } + transformer: UExprTransformer, + ): KeyMapper = { transformer.apply(it) } + + override fun map(composer: UComposer): UInputFieldRegionId = + this override fun toString(): String { return "inputField($field)" } } -interface UArrayId : URegionId { +interface UArrayId : URegionId { val arrayType: ArrayType } @@ -63,25 +70,26 @@ interface UArrayId : URegionId { * A region id for a region storing arrays allocated during execution. * Each identifier contains information about its [arrayType] and [address]. */ -data class UAllocatedArrayId internal constructor( +data class UAllocatedArrayId internal constructor( override val arrayType: ArrayType, val address: UConcreteHeapAddress, -) : UArrayId { + override val sort: Sort, +) : UArrayId { + override val defaultValue get() = sort.defaultValue() + @Suppress("UNCHECKED_CAST") - override fun read( - key: USizeExpr, - sort: Sort, + override fun read( heap: UReadOnlySymbolicHeap, + key: USizeExpr, ): UExpr { val ref = key.uctx.mkConcreteHeapRef(address) return heap.readArrayIndex(ref, key, arrayType as ArrayType, sort).asExpr(sort) } @Suppress("UNCHECKED_CAST") - override fun write( - key: USizeExpr, - sort: Sort, + override fun write( heap: USymbolicHeap, + key: USizeExpr, value: UExpr, guard: UBoolExpr, ) { @@ -90,15 +98,18 @@ data class UAllocatedArrayId internal constructor( } override fun keyMapper( - composer: UComposer, - ): KeyMapper = { composer.compose(it) } + transformer: UExprTransformer, + ): KeyMapper = { transformer.apply(it) } + + override fun map(composer: UComposer): UAllocatedArrayId = + this // we don't include arrayType into hashcode and equals, because [address] already defines unambiguously override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false - other as UAllocatedArrayId<*> + other as UAllocatedArrayId<*, *> if (address != other.address) return false @@ -117,33 +128,37 @@ data class UAllocatedArrayId internal constructor( /** * A region id for a region storing arrays retrieved as a symbolic value, contains only its [arrayType]. */ -data class UInputArrayId internal constructor( +data class UInputArrayId internal constructor( override val arrayType: ArrayType, -) : UArrayId { + override val sort: Sort, +) : UArrayId { + override val defaultValue get() = null + @Suppress("UNCHECKED_CAST") - override fun read( - key: USymbolicArrayIndex, - sort: Sort, + override fun read( heap: UReadOnlySymbolicHeap, + key: USymbolicArrayIndex, ): UExpr = heap.readArrayIndex(key.first, key.second, arrayType as ArrayType, sort).asExpr(sort) @Suppress("UNCHECKED_CAST") - override fun write( - key: USymbolicArrayIndex, - sort: Sort, + override fun write( heap: USymbolicHeap, + key: USymbolicArrayIndex, value: UExpr, guard: UBoolExpr, ) = heap.writeArrayIndex(key.first, key.second, arrayType as ArrayType, sort, value, guard) override fun keyMapper( - composer: UComposer, + transformer: UExprTransformer, ): KeyMapper = { - val ref = composer.compose(it.first) - val idx = composer.compose(it.second) + val ref = transformer.apply(it.first) + val idx = transformer.apply(it.second) if (ref === it.first && idx === it.second) it else ref to idx } + override fun map(composer: UComposer): UInputArrayId = + this + override fun toString(): String { return "inputArray($arrayType)" } @@ -154,20 +169,21 @@ data class UInputArrayId internal constructor( */ data class UInputArrayLengthId internal constructor( val arrayType: ArrayType, -) : URegionId { + override val sort: USizeSort, +) : URegionId { + override val defaultValue get() = null + @Suppress("UNCHECKED_CAST") - override fun read( - key: UHeapRef, - sort: Sort, + override fun read( heap: UReadOnlySymbolicHeap, - ): UExpr = heap.readArrayLength(key, arrayType as ArrayType).asExpr(sort) + key: UHeapRef, + ): UExpr = heap.readArrayLength(key, arrayType as ArrayType).asExpr(sort) @Suppress("UNCHECKED_CAST") - override fun write( - key: UHeapRef, - sort: Sort, + override fun write( heap: USymbolicHeap, - value: UExpr, + key: UHeapRef, + value: UExpr, guard: UBoolExpr, ) { assert(guard.isTrue) @@ -175,8 +191,11 @@ data class UInputArrayLengthId internal constructor( } override fun keyMapper( - composer: UComposer, - ): KeyMapper = { composer.compose(it) } + transformer: UExprTransformer, + ): KeyMapper = { transformer.apply(it) } + + override fun map(composer: UComposer): UInputArrayLengthId = + this override fun toString(): String { return "length($arrayType)" diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt new file mode 100644 index 0000000000..109a2389fa --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt @@ -0,0 +1,249 @@ +package org.usvm + +import org.ksmt.expr.KExpr +import org.ksmt.sort.KArray2Sort +import org.ksmt.sort.KArraySort +import org.ksmt.utils.mkConst +import org.usvm.util.Region +import org.usvm.util.RegionTree +import java.util.IdentityHashMap + +/** + * [URegionTranslator] defines a template method that translates a region reading to a specific [KExpr] with a sort [Sort]. + */ +internal class URegionTranslator, Key, Sort : USort, Result>( + private val updateTranslator: UUpdateTranslator, + private val updatesTranslator: UUpdatesTranslator, +) { + fun translateReading(region: UMemoryRegion, key: Key): KExpr { + val translated = translate(region) + return updateTranslator.select(translated, key) + } + + private val cache = IdentityHashMap, Result>() + + private fun translate(region: UMemoryRegion): Result = + cache.getOrPut(region) { updatesTranslator.translateUpdates(region.updates) } +} + +internal interface UUpdateTranslator { + fun select(result: Result, key: Key): KExpr + + fun initialValue(): Result + + fun applyUpdate( + previous: Result, + update: UUpdateNode, + ): Result +} + +internal class U1DArrayUpdateTranslator, Sort>, KeySort : USort, Sort : USort>( + private val translator: UExprTranslator<*, *>, + private val keySort: KeySort, + private val regionId: RegionId, +) : UUpdateTranslator, Sort, KExpr>> { + override fun select(result: KExpr>, key: KExpr): KExpr = + result.ctx.mkArraySelect(result, key) + + override fun initialValue(): KExpr> { + val ctx = regionId.sort.uctx + val arraySort = ctx.mkArraySort(keySort, regionId.sort) + val defaultValue = regionId.defaultValue + val initialArray = if (defaultValue == null) { + arraySort.mkConst(regionId.toString()) + } else { + ctx.mkArrayConst(arraySort, defaultValue) + } + return initialArray + } + + override fun applyUpdate( + previous: KExpr>, + update: UUpdateNode, Sort>, + ): KExpr> = with(previous.uctx) { + when (update) { + is UPinpointUpdateNode -> { + val key = update.key.translated + val value = update.value.translated + val guard = update.guard.translated + mkIte(guard, previous.store(key, value), previous) + // or + // previous.store(update.key, mkIte(update.guard, update.value, previous.select(update.key))) + } + + is URangedUpdateNode<*, *, *, *, *> -> { + when (update.guard) { + falseExpr -> previous + else -> { + @Suppress("UNCHECKED_CAST") + (update as URangedUpdateNode, *, Any?, UExpr, Sort>) + val key = mkFreshConst("k", previous.sort.domain) + + val from = update.region + val convertedKey = from.regionId.keyMapper(translator)(update.keyConverter.convert(key)) + val isInside = update.includesSymbolically(key).translated // already includes guard + val result = translator.translateRegionReading(from, convertedKey) + val ite = mkIte(isInside, result, previous.select(key)) + mkArrayLambda(key.decl, ite) + } + } + } + } + } + + private val KExpr.translated get() = translator.translate(this) +} + +internal class U2DArrayUpdateTranslator, KExpr>, Sort>, Key1Sort : USort, Key2Sort : USort, Sort : USort>( + private val translator: UExprTranslator<*, *>, + private val key1Sort: Key1Sort, + private val key2Sort: Key2Sort, + private val regionId: RegionId, +) : UUpdateTranslator, KExpr>, Sort, KExpr>> { + override fun select( + result: KExpr>, + key: Pair, KExpr>, + ): KExpr = + result.ctx.mkArraySelect(result, key.first.translated, key.second.translated) + + override fun initialValue(): KExpr> { + val ctx = regionId.sort.uctx + val arraySort = ctx.mkArraySort(key1Sort, key2Sort, regionId.sort) + + val defaultValue = regionId.defaultValue + val initialArray = if (defaultValue == null) { + arraySort.mkConst(regionId.toString()) + } else { + ctx.mkArrayConst(arraySort, defaultValue.translated) + } + + return initialArray + } + + override fun applyUpdate( + previous: KExpr>, + update: UUpdateNode, KExpr>, Sort>, + ): KExpr> = with(previous.uctx) { + when (update) { + is UPinpointUpdateNode -> { + val key1 = update.key.first.translated + val key2 = update.key.second.translated + val value = update.value.translated + val guard = update.guard.translated + mkIte(guard, previous.store(key1, key2, value), previous) + } + + is URangedUpdateNode<*, *, *, *, *> -> { + when (update.guard) { + falseExpr -> previous + else -> { + @Suppress("UNCHECKED_CAST") + (update as URangedUpdateNode, *, Any?, Pair, UExpr>, Sort>) + val key1 = mkFreshConst("k1", previous.sort.domain0) + val key2 = mkFreshConst("k2", previous.sort.domain1) + + val region = update.region + val convertedKey = region.regionId.keyMapper(translator)(update.keyConverter.convert(key1 to key2)) + val isInside = update.includesSymbolically(key1 to key2).translated // already includes guard + val result = translator.translateRegionReading(region, convertedKey) + val ite = mkIte(isInside, result, previous.select(key1, key2)) + mkArrayLambda(key1.decl, key2.decl, ite) + } + } + } + } + } + + private val UExpr.translated get() = translator.translate(this) +} + +internal interface UUpdatesTranslator { + fun translateUpdates(updates: UMemoryUpdates): Result +} + +internal class UFlatUpdatesTranslator( + private val regionTranslator: UUpdateTranslator, +) : UUpdatesTranslator { + private val cache: IdentityHashMap, Result> = IdentityHashMap() + + override fun translateUpdates( + updates: UMemoryUpdates, + ): Result = + when (updates) { + is UFlatUpdates -> translateFlatUpdate(updates) + is UEmptyUpdates -> regionTranslator.initialValue() + else -> error("This updates translator works only with UFlatUpdates or UEmptyUpdates") + } + + private fun translateFlatUpdate(updates: UFlatUpdates): Result { + val result = cache.getOrPut(updates) { + val accumulated = updates.next?.let(::translateUpdates) ?: regionTranslator.initialValue() + regionTranslator.applyUpdate(accumulated, updates.node) + } + return result + } +} + +internal class UTreeUpdatesTranslator( + private val regionTranslator: UUpdateTranslator, +) : UUpdatesTranslator { + private val cache: IdentityHashMap, *>, Result> = IdentityHashMap() + + override fun translateUpdates(updates: UMemoryUpdates, ): Result { + require(updates is UTreeUpdates) { "This updates translator works only with UTreeUpdates" } + + return cache.getOrPut(updates.updates) { + Builder(updates).leftMostTranslate(updates.updates) + } + } + + private inner class Builder( + private val treeUpdates: UTreeUpdates, + ) { + private val emittedUpdates = hashSetOf>() + + fun leftMostTranslate(updates: RegionTree, *>): Result { + var result = cache[updates] + + if (result != null) { + return result + } + + val entryIterator = updates.entries.iterator() + if (!entryIterator.hasNext()) { + return regionTranslator.initialValue() + } + val (update, nextUpdates) = entryIterator.next().value + result = leftMostTranslate(nextUpdates) + result = regionTranslator.applyUpdate(result, update) + return notLeftMostTranslate(result, entryIterator) + } + + private fun notLeftMostTranslate( + accumulator: Result, + iterator: Iterator, Pair, RegionTree, *>>>>, + ): Result { + var accumulated = accumulator + while (iterator.hasNext()) { + val (reg, entry) = iterator.next() + val (update, tree) = entry + accumulated = notLeftMostTranslate(accumulated, tree.entries.iterator()) + + accumulated = addIfNeeded(accumulated, update, reg) + } + return accumulated + } + + private fun addIfNeeded(accumulated: Result, update: UUpdateNode, region: Region<*>): Result { + if (treeUpdates.checkWasCloned(update, region)) { + if (update in emittedUpdates) { + return accumulated + } + emittedUpdates += update + regionTranslator.applyUpdate(accumulated, update) + } + + return regionTranslator.applyUpdate(accumulated, update) + } + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/UExprTransformer.kt b/usvm-core/src/main/kotlin/org/usvm/UExprTransformer.kt index 849e4aa4f0..9d9d54c78a 100644 --- a/usvm-core/src/main/kotlin/org/usvm/UExprTransformer.kt +++ b/usvm-core/src/main/kotlin/org/usvm/UExprTransformer.kt @@ -9,7 +9,7 @@ abstract class UExprTransformer(ctx: UContext): KNonRecursiveTransf abstract fun transform(expr: UHeapReading<*, *, *>): UExpr abstract fun transform(expr: UInputFieldReading): UExpr - abstract fun transform(expr : UAllocatedArrayReading): UExpr + abstract fun transform(expr: UAllocatedArrayReading): UExpr abstract fun transform(expr: UInputArrayReading): UExpr abstract fun transform(expr: UInputArrayLengthReading): USizeExpr diff --git a/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt b/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt index bffac5beeb..c57be7432b 100644 --- a/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt +++ b/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt @@ -153,7 +153,7 @@ sealed class UMemoryKeyConverter( abstract fun convert(key: DstKey): SrcKey protected fun convertIndex(idx: USizeExpr): USizeExpr = with(srcSymbolicArrayIndex.first.ctx) { - return mkBvSubExpr(mkBvAddExpr(idx, dstFromSymbolicArrayIndex.second), srcSymbolicArrayIndex.second) + mkBvSubExpr(mkBvAddExpr(idx, dstFromSymbolicArrayIndex.second), srcSymbolicArrayIndex.second) } abstract fun clone( @@ -194,7 +194,7 @@ sealed class UMemoryKeyConverter( * with values from memory region [region] read from range * of addresses [[keyConverter].convert([fromKey]) : [keyConverter].convert([toKey])] */ -class URangedUpdateNode, ArrayType, SrcKey, DstKey, ValueSort : USort>( +class URangedUpdateNode, ArrayType, SrcKey, DstKey, ValueSort : USort>( val fromKey: DstKey, val toKey: DstKey, val region: UMemoryRegion, @@ -352,7 +352,7 @@ class UAllocatedToInputKeyConverter( } /** - * Used when copying data from allocated input to allocated one. + * Used when copying data from input array to allocated one. */ class UInputToAllocatedKeyConverter( srcSymbolicArrayIndex: USymbolicArrayIndex, diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index b3b1d813e5..d7570f33f1 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -199,12 +199,10 @@ internal class CompositionTest { ).write(fstAddress, fstResultValue, guard = trueExpr) .write(sndAddress, sndResultValue, guard = trueExpr) - val regionId = UInputArrayLengthId(arrayType) + val regionId = UInputArrayLengthId(arrayType, bv32Sort) val regionArray = UInputArrayLengthRegion( regionId, - bv32Sort, updates, - defaultValue = sizeSort.defaultValue(), instantiator = { key, region -> mkInputArrayLengthReading(region, key) } ) @@ -264,10 +262,8 @@ internal class CompositionTest { val arrayType: KClass> = Array::class val region = UInputArrayRegion( - UInputArrayId(arrayType), - mkBv32Sort(), + UInputArrayId(arrayType, bv32Sort), updates, - defaultValue = null, instantiator = { key, memoryRegion -> mkInputArrayReading(memoryRegion, key.first, key.second) } ) @@ -372,12 +368,10 @@ internal class CompositionTest { ).write(fstIndex, 1.toBv(), guard = trueExpr) .write(sndIndex, 2.toBv(), guard = trueExpr) - val regionId = UAllocatedArrayId(arrayType, address) + val regionId = UAllocatedArrayId(arrayType, address, bv32Sort) val regionArray = UAllocatedArrayRegion( regionId, - bv32Sort, updates, - defaultValue = 0.toBv(), instantiator = { key, region -> mkAllocatedArrayReading(region, key) } ) @@ -429,10 +423,8 @@ internal class CompositionTest { // An empty region with one write in it val region = UInputFieldRegion( - UInputFieldRegionId(field), - bv32Sort, + UInputFieldRegionId(field, bv32Sort), updates, - defaultValue = null, instantiator = { key, region -> mkInputFieldReading(region, key) } ).write(aAddress, 43.toBv(), guard = trueExpr) diff --git a/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt index 79df0060ab..fd586c6860 100644 --- a/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt @@ -152,7 +152,7 @@ class MapCompositionTest { val addr = addressSort.mkConst("addr") val fromKey = sizeSort.mkConst("fromKey") as UExpr val toKey = sizeSort.mkConst("toKey") as UExpr - val region = mockk, UExpr, UBv32Sort>>() + val region = mockk, UExpr, UBv32Sort>>() val guard = boolSort.mkConst("guard") val updateNode = URangedUpdateNode( @@ -185,7 +185,7 @@ class MapCompositionTest { val addr = mkConcreteHeapRef(0) val fromKey = sizeSort.mkConst("fromKey") val toKey = sizeSort.mkConst("toKey") - val region = mockk, USizeExpr, UBv32Sort>>() + val region = mockk, USizeExpr, UBv32Sort>>() val guard = boolSort.mkConst("guard") val updateNode = URangedUpdateNode( @@ -204,7 +204,7 @@ class MapCompositionTest { val composedFromKey = sizeSort.mkConst("composedFromKey") val composedToKey = sizeSort.mkConst("composedToKey") - val composedRegion = mockk, UExpr, UBv32Sort>>() + val composedRegion = mockk, UExpr, UBv32Sort>>() val composedGuard = mkTrue() every { composer.compose(addr) } returns addr diff --git a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt new file mode 100644 index 0000000000..dceadee719 --- /dev/null +++ b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt @@ -0,0 +1,158 @@ +package org.usvm + +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.ksmt.utils.mkConst +import kotlin.test.assertSame + +class TranslationTest { + private lateinit var ctx: UContext + private lateinit var heap: URegionHeap + private lateinit var translator: UExprTranslator + + private lateinit var valueFieldDescr: Pair + private lateinit var addressFieldDescr: Pair + private lateinit var valueArrayDescr: ArrayType + private lateinit var addressArrayDescr: ArrayType + + + @BeforeEach + fun initializeContext() { + ctx = UContext() + heap = URegionHeap(ctx) + translator = UExprTranslator(ctx) + + valueFieldDescr = mockk() to ctx.bv32Sort + addressFieldDescr = mockk() to ctx.addressSort + valueArrayDescr = mockk() + addressArrayDescr = mockk() + } + + @Test + @Disabled("TODO: rebase on ref splitting PR") + fun testTranslateConstAddressSort() = with(ctx) { + val ref = mkConcreteHeapRef(heap.allocate()) + val idx = mkRegisterReading(0, sizeSort) + + val expr = heap.readArrayIndex(ref, idx, addressArrayDescr, addressSort) + val translated = translator.translate(expr) + + assertSame(nullRef, translated) + } + + @Test + fun testTranslateConstValueSort() = with(ctx) { + val ref = mkConcreteHeapRef(heap.allocate()) + val idx = mkRegisterReading(0, sizeSort) + + val expr = heap.readArrayIndex(ref, idx, valueArrayDescr, bv32Sort) + val translated = translator.translate(expr) + + assertSame(bv32Sort.defaultValue(), translated) + } + + @Test + fun testTranslateWritingsToAllocatedArray() = with(ctx) { + val ref = mkConcreteHeapRef(heap.allocate()) + val idx1 = mkRegisterReading(0, sizeSort) + val idx2 = mkRegisterReading(1, sizeSort) + + val val1 = mkBv(1) + val val2 = mkBv(2) + + heap.writeArrayIndex(ref, idx1, valueArrayDescr, bv32Sort, val1, trueExpr) + heap.writeArrayIndex(ref, idx2, valueArrayDescr, bv32Sort, val2, trueExpr) + + val readIdx = mkRegisterReading(2, sizeSort) + + val expr = heap.readArrayIndex(ref, readIdx, valueArrayDescr, bv32Sort) + + val translated = translator.translate(expr) + + val translatedIdx1 = translator.translate(idx1) + val translatedIdx2 = translator.translate(idx2) + val translatedReadIdx = translator.translate(readIdx) + + val expected = mkArrayConst(mkArraySort(sizeSort, bv32Sort), bv32Sort.defaultValue()) + .store(translatedIdx1, val1) + .store(translatedIdx2, val2) + .select(translatedReadIdx) + + assertSame(expected, translated) + } + + @Test + fun testTranslate2dArray() = with(ctx) { + var region = emptyInputArrayRegion(valueArrayDescr, bv32Sort) { (ref, idx), reg -> + mkInputArrayReading(reg, ref, idx) + } + + val ref1 = mkRegisterReading(0, addressSort) + val idx1 = mkRegisterReading(1, sizeSort) + val val1 = mkBv(1) + + val ref2 = mkRegisterReading(2, addressSort) + val idx2 = mkRegisterReading(3, sizeSort) + val val2 = mkBv(2) + + + region = region.write(ref1 to idx1, val1, trueExpr) + region = region.write(ref2 to idx2, val2, trueExpr) + + val ref3 = mkRegisterReading(4, addressSort) + val idx3 = mkRegisterReading(5, sizeSort) + + val reading = region.read(ref3 to idx3) + + val translated = translator.translate(reading) + + val expected = mkArraySort(addressSort, sizeSort, bv32Sort) + .mkConst(region.regionId.toString()) + .store(translator.translate(ref1), translator.translate(idx1), val1) + .store(translator.translate(ref2), translator.translate(idx2), val2) + .select(translator.translate(ref3), translator.translate(idx3)) + + assertSame(expected, translated) + } + + @Test + fun testTranslateArrayCopy() = with(ctx) { + var region = emptyInputArrayRegion(valueArrayDescr, bv32Sort) { (ref, idx), reg -> + mkInputArrayReading(reg, ref, idx) + } + + val ref1 = mkRegisterReading(0, addressSort) + val idx1 = mkRegisterReading(1, sizeSort) + val val1 = mkBv(1) + + val ref2 = mkRegisterReading(2, addressSort) + val idx2 = mkRegisterReading(3, sizeSort) + val val2 = mkBv(2) + + region = region.write(ref1 to idx1, val1, trueExpr) + region = region.write(ref2 to idx2, val2, trueExpr) + + val concreteRef = mkConcreteHeapRef(heap.allocate()) + + var concreteRegion = emptyAllocatedArrayRegion(valueArrayDescr, concreteRef.address, bv32Sort) { idx, reg -> + mkAllocatedArrayReading(reg, idx) + } + val keyConverter = UInputToAllocatedKeyConverter(ref1 to mkBv(0), concreteRef to mkBv(0), mkBv(5)) + concreteRegion = concreteRegion.copyRange(region, mkBv(0), mkBv(5), keyConverter, trueExpr) + + val idx = mkRegisterReading(4, sizeSort) + val reading = concreteRegion.read(idx) + + + val innerReading = + translator.translateRegionReading(region, keyConverter.convert(translator.translate(idx))) + val guard = translator.translate((mkBvSignedLessOrEqualExpr(mkBv(0), idx)) and mkBvSignedLessOrEqualExpr(idx, mkBv(5))) + val expected = mkIte(guard, innerReading, bv32Sort.defaultValue()) + + val translated = translator.translate(reading) + + assertSame(expected, translated) + } +} \ No newline at end of file From 79a0b1c2f5bbb9fdc4b26fb47ac73b6b9d45ee59 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Wed, 5 Apr 2023 10:30:32 +0300 Subject: [PATCH 02/28] Add: some fixes from the DSL pr --- usvm-core/src/main/kotlin/org/usvm/Context.kt | 21 ++++++++----------- usvm-core/src/main/kotlin/org/usvm/Heap.kt | 18 +++++++++------- .../src/main/kotlin/org/usvm/RegionIds.kt | 3 ++- .../main/kotlin/org/usvm/RegionTranslator.kt | 18 ++++++++-------- .../test/kotlin/org/usvm/CompositionTest.kt | 3 ++- .../test/kotlin/org/usvm/TranslationTest.kt | 7 ++++--- 6 files changed, 37 insertions(+), 33 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/Context.kt b/usvm-core/src/main/kotlin/org/usvm/Context.kt index 04dca69f85..fec806f63e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Context.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Context.kt @@ -2,8 +2,9 @@ package org.usvm import org.ksmt.KAst import org.ksmt.KContext +import org.ksmt.expr.KExpr import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue -import org.ksmt.utils.asExpr +import org.ksmt.sort.KUninterpretedSort import org.ksmt.utils.cast @Suppress("LeakingThis") @@ -15,7 +16,7 @@ open class UContext( val addressSort: UAddressSort = mkUninterpretedSort("Address") val sizeSort: USizeSort = bv32Sort - val zeroSize: USizeExpr = sizeSort.defaultValue() + val zeroSize: USizeExpr = sizeSort.sampleValue() val nullRef: USymbolicHeapRef = UNullRef(this) @@ -144,18 +145,14 @@ open class UContext( UIsExpr(this, ref, type.cast()) }.cast() - fun mkDefault(sort: Sort): UExpr = - when (sort) { - addressSort -> nullRef.asExpr(sort) - else -> sort.sampleValue() + override fun uninterpretedSortDefaultValue(sort: KUninterpretedSort): KExpr = + if (sort == addressSort) { + nullRef + } else { + super.uninterpretedSortDefaultValue(sort) } -} -fun Sort.defaultValue() = - when (ctx) { - is UContext -> (ctx as UContext).mkDefault(this) - else -> sampleValue() - } +} val KAst.uctx get() = ctx as UContext diff --git a/usvm-core/src/main/kotlin/org/usvm/Heap.kt b/usvm-core/src/main/kotlin/org/usvm/Heap.kt index 725efcb1f0..59ed2a908f 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Heap.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Heap.kt @@ -1,8 +1,12 @@ package org.usvm +import org.ksmt.solver.KModel +import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.utils.asExpr +import org.ksmt.utils.cast import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.toPersistentMap interface UReadOnlyHeap { fun readField(ref: Ref, field: Field, sort: Sort): Value @@ -19,14 +23,14 @@ typealias UReadOnlySymbolicHeap = UReadOnlyHeap(private val ctx: UContext) : UReadOnlySymbolicHeap { override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr = - ctx.mkDefault(sort) + sort.sampleValue() override fun readArrayIndex( ref: UHeapRef, index: USizeExpr, arrayType: ArrayType, elementSort: Sort, - ): UExpr = ctx.mkDefault(elementSort) + ): UExpr = elementSort.sampleValue() override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType) = ctx.zeroSize @@ -84,7 +88,7 @@ data class URegionHeap( private val ctx: UContext, private var lastAddress: UAddressCounter = UAddressCounter(), private var allocatedFields: PersistentMap, UExpr> = persistentMapOf(), - private var inputFields: PersistentMap> = persistentMapOf(), + private var inputFields: PersistentMap> = persistentMapOf(), private var allocatedArrays: PersistentMap> = persistentMapOf(), private var inputArrays: PersistentMap> = persistentMapOf(), private var allocatedLengths: PersistentMap = persistentMapOf(), @@ -130,7 +134,7 @@ data class URegionHeap( ref.map( { concreteRef -> allocatedFields - .getOrDefault(concreteRef.address to field, sort.defaultValue()) + .getOrDefault(concreteRef.address to field, sort.sampleValue()) .asExpr(sort) }, { symbolicRef -> inputFieldRegion(field, sort).read(symbolicRef) } @@ -200,8 +204,8 @@ data class URegionHeap( allocatedArrays = allocatedArrays.put(concreteRef.address, newRegion) }, { (symbolicRef, innerGuard) -> - val region = inputArrayRegion(type, elementSort) - val newRegion = region.write(symbolicRef to index, valueToWrite, innerGuard) + val oldRegion = inputArrayRegion(type, elementSort) + val newRegion = oldRegion.write(symbolicRef to index, valueToWrite, innerGuard) inputArrays = inputArrays.put(type, newRegion) } ) @@ -308,7 +312,7 @@ data class URegionHeap( return address } - override fun clone(): UHeap, USizeExpr, Field, ArrayType, UBoolExpr> = + override fun clone(): URegionHeap = URegionHeap( ctx, lastAddress, allocatedFields, inputFields, diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt index 2da665b52f..95cbac1c79 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt @@ -1,5 +1,6 @@ package org.usvm +import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.utils.asExpr @@ -75,7 +76,7 @@ data class UAllocatedArrayId internal constructor( val address: UConcreteHeapAddress, override val sort: Sort, ) : UArrayId { - override val defaultValue get() = sort.defaultValue() + override val defaultValue get() = sort.sampleValue() @Suppress("UNCHECKED_CAST") override fun read( diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt index 109a2389fa..6f4263478c 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt @@ -162,7 +162,7 @@ internal interface UUpdatesTranslator { } internal class UFlatUpdatesTranslator( - private val regionTranslator: UUpdateTranslator, + private val updateTranslator: UUpdateTranslator, ) : UUpdatesTranslator { private val cache: IdentityHashMap, Result> = IdentityHashMap() @@ -171,21 +171,21 @@ internal class UFlatUpdatesTranslator( ): Result = when (updates) { is UFlatUpdates -> translateFlatUpdate(updates) - is UEmptyUpdates -> regionTranslator.initialValue() + is UEmptyUpdates -> updateTranslator.initialValue() else -> error("This updates translator works only with UFlatUpdates or UEmptyUpdates") } private fun translateFlatUpdate(updates: UFlatUpdates): Result { val result = cache.getOrPut(updates) { - val accumulated = updates.next?.let(::translateUpdates) ?: regionTranslator.initialValue() - regionTranslator.applyUpdate(accumulated, updates.node) + val accumulated = updates.next?.let(::translateUpdates) ?: updateTranslator.initialValue() + updateTranslator.applyUpdate(accumulated, updates.node) } return result } } internal class UTreeUpdatesTranslator( - private val regionTranslator: UUpdateTranslator, + private val updateTranslator: UUpdateTranslator, ) : UUpdatesTranslator { private val cache: IdentityHashMap, *>, Result> = IdentityHashMap() @@ -211,11 +211,11 @@ internal class UTreeUpdatesTranslator( val entryIterator = updates.entries.iterator() if (!entryIterator.hasNext()) { - return regionTranslator.initialValue() + return updateTranslator.initialValue() } val (update, nextUpdates) = entryIterator.next().value result = leftMostTranslate(nextUpdates) - result = regionTranslator.applyUpdate(result, update) + result = updateTranslator.applyUpdate(result, update) return notLeftMostTranslate(result, entryIterator) } @@ -240,10 +240,10 @@ internal class UTreeUpdatesTranslator( return accumulated } emittedUpdates += update - regionTranslator.applyUpdate(accumulated, update) + updateTranslator.applyUpdate(accumulated, update) } - return regionTranslator.applyUpdate(accumulated, update) + return updateTranslator.applyUpdate(accumulated, update) } } } diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index d7570f33f1..a296bd0380 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -11,6 +11,7 @@ import org.ksmt.expr.KBitVec32Value import org.ksmt.expr.KExpr import org.ksmt.expr.printer.ExpressionPrinter import org.ksmt.expr.transformer.KTransformerBase +import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.sort.KBv32Sort import kotlin.reflect.KClass import kotlin.test.assertEquals @@ -381,7 +382,7 @@ internal class CompositionTest { val fstAddressForCompose = mkConcreteHeapRef(address) val sndAddressForCompose = mkConcreteHeapRef(address) - val concreteIndex = sizeSort.defaultValue() + val concreteIndex = sizeSort.sampleValue() val fstValue = 42.toBv() val sndValue = 43.toBv() diff --git a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt index dceadee719..6b06ef0ea2 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt @@ -4,6 +4,7 @@ import io.mockk.mockk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.utils.mkConst import kotlin.test.assertSame @@ -50,7 +51,7 @@ class TranslationTest { val expr = heap.readArrayIndex(ref, idx, valueArrayDescr, bv32Sort) val translated = translator.translate(expr) - assertSame(bv32Sort.defaultValue(), translated) + assertSame(bv32Sort.sampleValue(), translated) } @Test @@ -75,7 +76,7 @@ class TranslationTest { val translatedIdx2 = translator.translate(idx2) val translatedReadIdx = translator.translate(readIdx) - val expected = mkArrayConst(mkArraySort(sizeSort, bv32Sort), bv32Sort.defaultValue()) + val expected = mkArrayConst(mkArraySort(sizeSort, bv32Sort), bv32Sort.sampleValue()) .store(translatedIdx1, val1) .store(translatedIdx2, val2) .select(translatedReadIdx) @@ -149,7 +150,7 @@ class TranslationTest { val innerReading = translator.translateRegionReading(region, keyConverter.convert(translator.translate(idx))) val guard = translator.translate((mkBvSignedLessOrEqualExpr(mkBv(0), idx)) and mkBvSignedLessOrEqualExpr(idx, mkBv(5))) - val expected = mkIte(guard, innerReading, bv32Sort.defaultValue()) + val expected = mkIte(guard, innerReading, bv32Sort.sampleValue()) val translated = translator.translate(reading) From b349006d459f514707475db5fefdf34c192dd43e Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Wed, 5 Apr 2023 17:06:24 +0300 Subject: [PATCH 03/28] Remove: UEmptyUpdates --- .../src/main/kotlin/org/usvm/MemoryRegions.kt | 4 +- .../src/main/kotlin/org/usvm/MemoryUpdates.kt | 110 ++++++------------ .../main/kotlin/org/usvm/RegionTranslator.kt | 22 ++-- .../test/kotlin/org/usvm/CompositionTest.kt | 20 +--- .../kotlin/org/usvm/MapCompositionTest.kt | 14 +-- .../kotlin/org/usvm/UpdatesIteratorTest.kt | 15 +-- 6 files changed, 62 insertions(+), 123 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt index 725509eaa6..d64bbaf390 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt @@ -330,7 +330,7 @@ fun emptyInputFieldRegion( instantiator: UInstantiator, UHeapRef, Sort>, ): UInputFieldRegion = UMemoryRegion( UInputFieldRegionId(field, sort), - UEmptyUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic), + UFlatUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic), instantiator ) @@ -367,7 +367,7 @@ fun emptyArrayLengthRegion( ): UInputArrayLengthRegion = UMemoryRegion( UInputArrayLengthId(arrayType, sizeSort), - UEmptyUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic), + UFlatUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic), instantiator ) diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt index e182ec50e4..ec1b84bc35 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt @@ -74,73 +74,28 @@ interface UMemoryUpdates : Sequence> { //region Flat memory updates -class UEmptyUpdates( +class UFlatUpdates private constructor( + internal val node: FlatNode?, private val symbolicEq: (Key, Key) -> UBoolExpr, private val concreteCmp: (Key, Key) -> Boolean, private val symbolicCmp: (Key, Key) -> UBoolExpr, ) : UMemoryUpdates { - override fun read(key: Key): UEmptyUpdates = this - - override fun write(key: Key, value: UExpr, guard: UBoolExpr): UFlatUpdates = - UFlatUpdates( - UPinpointUpdateNode(key, value, symbolicEq, guard), - next = null, - symbolicEq, - concreteCmp, - symbolicCmp - ) - - override fun , SrcKey> copyRange( - fromRegion: UMemoryRegion, - fromKey: Key, - toKey: Key, - keyConverter: UMemoryKeyConverter, - guard: UBoolExpr, - ): UFlatUpdates = UFlatUpdates( - URangedUpdateNode(fromKey, toKey, fromRegion, concreteCmp, symbolicCmp, keyConverter, guard), - next = null, - symbolicEq, - concreteCmp, - symbolicCmp + constructor( + symbolicEq: (Key, Key) -> UBoolExpr, + concreteCmp: (Key, Key) -> Boolean, + symbolicCmp: (Key, Key) -> UBoolExpr, + ) : this(null, symbolicEq, concreteCmp, symbolicCmp) + + internal data class FlatNode( + val update: UUpdateNode, + val next: UFlatUpdates, ) - override fun split( - key: Key, - predicate: (UExpr) -> Boolean, - matchingWrites: MutableList>>, - guardBuilder: GuardBuilder, - ) = this - - override fun map( - keyMapper: KeyMapper, - composer: UComposer, - ): UEmptyUpdates = this - - override fun iterator(): Iterator> = EmptyIterator() - - private class EmptyIterator : Iterator> { - override fun hasNext(): Boolean = false - override fun next(): UUpdateNode = error("Advancing empty iterator") - } - - override fun lastUpdatedElementOrNull(): UUpdateNode? = null - - override fun isEmpty(): Boolean = true -} - -data class UFlatUpdates( - val node: UUpdateNode, - val next: UMemoryUpdates?, - private val symbolicEq: (Key, Key) -> UBoolExpr, - private val concreteCmp: (Key, Key) -> Boolean, - private val symbolicCmp: (Key, Key) -> UBoolExpr, -) : UMemoryUpdates { override fun read(key: Key): UMemoryUpdates = this override fun write(key: Key, value: UExpr, guard: UBoolExpr): UFlatUpdates = UFlatUpdates( - UPinpointUpdateNode(key, value, symbolicEq, guard), - next = this, + FlatNode(UPinpointUpdateNode(key, value, symbolicEq, guard), this), symbolicEq, concreteCmp, symbolicCmp @@ -153,8 +108,7 @@ data class UFlatUpdates( keyConverter: UMemoryKeyConverter, guard: UBoolExpr, ): UMemoryUpdates = UFlatUpdates( - URangedUpdateNode(fromKey, toKey, fromRegion, concreteCmp, symbolicCmp, keyConverter, guard), - next = this, + FlatNode(URangedUpdateNode(fromKey, toKey, fromRegion, concreteCmp, symbolicCmp, keyConverter, guard), this), symbolicEq, concreteCmp, symbolicCmp @@ -165,36 +119,38 @@ data class UFlatUpdates( predicate: (UExpr) -> Boolean, matchingWrites: MutableList>>, guardBuilder: GuardBuilder, - ): UMemoryUpdates { - val splitNode = node.split(key, predicate, matchingWrites, guardBuilder) - val splitNext = next?.split(key, predicate, matchingWrites, guardBuilder) + ): UFlatUpdates { + node ?: return this + val splitNode = node.update.split(key, predicate, matchingWrites, guardBuilder) + val splitNext = node.next.split(key, predicate, matchingWrites, guardBuilder) if (splitNode == null) { - return splitNext ?: UEmptyUpdates(symbolicEq, concreteCmp, symbolicCmp) + return splitNext } - if (splitNext === next) { + if (splitNext === node.next && splitNode === node.update) { return this } - return UFlatUpdates(splitNode, splitNext, symbolicEq, concreteCmp, symbolicCmp) + return UFlatUpdates(FlatNode(splitNode, splitNext), symbolicEq, concreteCmp, symbolicCmp) } override fun map( keyMapper: KeyMapper, composer: UComposer, ): UFlatUpdates { + node ?: return this // Map the current node and the next values recursively - val mappedNode = node.map(keyMapper, composer) - val mappedNext = next?.map(keyMapper, composer) + val mappedNode = node.update.map(keyMapper, composer) + val mappedNext = node.next.map(keyMapper, composer) // If nothing changed, return this updates - if (mappedNode === node && mappedNext === next) { + if (mappedNode === node.update && mappedNext === node.next) { return this } // Otherwise, construct a new one using the mapped values - return UFlatUpdates(mappedNode, mappedNext, symbolicEq, concreteCmp, symbolicCmp) + return UFlatUpdates(FlatNode(mappedNode, mappedNext), symbolicEq, concreteCmp, symbolicCmp) } /** @@ -210,14 +166,13 @@ data class UFlatUpdates( init { val elements = mutableListOf>() - var current: UFlatUpdates? = initialNode + var current: UFlatUpdates = initialNode // Traverse over linked list of updates nodes and extract them into an array list - while (current != null) { - elements += current.node - // We can safely apply `as?` since we are interested only in non-empty updates - // and there are no `treeUpdates` as a `next` element of the `UFlatUpdates` - current = current.next as? UFlatUpdates + while (true) { + val node = current.node ?: break + elements += node.update + current = node.next } iterator = elements.asReversed().iterator() @@ -228,9 +183,9 @@ data class UFlatUpdates( override fun next(): UUpdateNode = iterator.next() } - override fun lastUpdatedElementOrNull(): UUpdateNode = node + override fun lastUpdatedElementOrNull(): UUpdateNode? = node?.update - override fun isEmpty(): Boolean = false + override fun isEmpty(): Boolean = node == null } //endregion @@ -453,6 +408,7 @@ data class UTreeUpdates, Sort : USort>( throw NoSuchElementException() } } + internal fun checkWasCloned(update: UUpdateNode, region: Region<*>): Boolean { // To check, whether we have a duplicate for a particular key, // we have to check if an initial region (by USVM estimation) is equal diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt index 6f4263478c..3d05bf1125 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt @@ -31,10 +31,7 @@ internal interface UUpdateTranslator { fun initialValue(): Result - fun applyUpdate( - previous: Result, - update: UUpdateNode, - ): Result + fun applyUpdate(previous: Result, update: UUpdateNode): Result } internal class U1DArrayUpdateTranslator, Sort>, KeySort : USort, Sort : USort>( @@ -143,7 +140,8 @@ internal class U2DArrayUpdateTranslator( updates: UMemoryUpdates, ): Result = when (updates) { - is UFlatUpdates -> translateFlatUpdate(updates) - is UEmptyUpdates -> updateTranslator.initialValue() - else -> error("This updates translator works only with UFlatUpdates or UEmptyUpdates") + is UFlatUpdates -> translateFlatUpdates(updates) + else -> error("This updates translator works only with UFlatUpdates") } - private fun translateFlatUpdate(updates: UFlatUpdates): Result { + private fun translateFlatUpdates(updates: UFlatUpdates): Result { val result = cache.getOrPut(updates) { - val accumulated = updates.next?.let(::translateUpdates) ?: updateTranslator.initialValue() - updateTranslator.applyUpdate(accumulated, updates.node) + val node = updates.node ?: return@getOrPut updateTranslator.initialValue() + val accumulated = translateUpdates(node.next) + updateTranslator.applyUpdate(accumulated, node.update) } return result } @@ -189,7 +187,7 @@ internal class UTreeUpdatesTranslator( ) : UUpdatesTranslator { private val cache: IdentityHashMap, *>, Result> = IdentityHashMap() - override fun translateUpdates(updates: UMemoryUpdates, ): Result { + override fun translateUpdates(updates: UMemoryUpdates): Result { require(updates is UTreeUpdates) { "This updates translator works only with UTreeUpdates" } return cache.getOrPut(updates.updates) { diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index a296bd0380..69df6bd48b 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -193,7 +193,7 @@ internal class CompositionTest { val fstResultValue = 1.toBv() val sndResultValue = 2.toBv() - val updates = UEmptyUpdates( + val updates = UFlatUpdates( symbolicEq = { k1, k2 -> k1 eq k2 }, concreteCmp = { _, _ -> throw UnsupportedOperationException() }, symbolicCmp = { _, _ -> throw UnsupportedOperationException() } @@ -245,20 +245,12 @@ internal class CompositionTest { mkAnd(k1.first eq k2.first, k1.second eq k2.second) } - val initialNode = UPinpointUpdateNode( - fstAddress to fstIndex, - 42.toBv(), - keyEqualityComparer, - trueExpr, - ) - - val updates: UMemoryUpdates = UFlatUpdates( - initialNode, - next = null, + val updates = UFlatUpdates( symbolicCmp = { _, _ -> shouldNotBeCalled() }, concreteCmp = { k1, k2 -> k1 == k2 }, symbolicEq = { k1, k2 -> keyEqualityComparer(k1, k2) } - ).write(sndAddress to sndIndex, 43.toBv(), guard = trueExpr) + ).write(fstAddress to fstIndex, 42.toBv(), guard = trueExpr) + .write(sndAddress to sndIndex, 43.toBv(), guard = trueExpr) val arrayType: KClass> = Array::class @@ -362,7 +354,7 @@ internal class CompositionTest { val fstSymbolicIndex = mockk() val sndSymbolicIndex = mockk() - val updates = UEmptyUpdates( + val updates = UFlatUpdates( symbolicEq = { k1, k2 -> k1 eq k2 }, concreteCmp = { _, _ -> throw UnsupportedOperationException() }, symbolicCmp = { _, _ -> throw UnsupportedOperationException() } @@ -415,7 +407,7 @@ internal class CompositionTest { val aAddress = mockk() val bAddress = mockk() - val updates = UEmptyUpdates( + val updates = UFlatUpdates( symbolicEq = { k1, k2 -> k1 eq k2 }, concreteCmp = { _, _ -> throw UnsupportedOperationException() }, symbolicCmp = { _, _ -> throw UnsupportedOperationException() } diff --git a/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt index fd586c6860..df539f71b3 100644 --- a/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt @@ -224,7 +224,7 @@ class MapCompositionTest { @Test fun testEmptyUpdatesMapOperation() { - val emptyUpdates = UEmptyUpdates, UBv32Sort>( + val emptyUpdates = UFlatUpdates, UBv32Sort>( { _, _ -> shouldNotBeCalled() }, { _, _ -> shouldNotBeCalled() }, { _, _ -> shouldNotBeCalled() } @@ -242,7 +242,7 @@ class MapCompositionTest { val sndKey = addressSort.mkConst("sndKey") val sndValue = bv32Sort.mkConst("sndValue") - val flatUpdates = UEmptyUpdates, UBv32Sort>( + val flatUpdates = UFlatUpdates, UBv32Sort>( { _, _ -> shouldNotBeCalled() }, { _, _ -> shouldNotBeCalled() }, { _, _ -> shouldNotBeCalled() } @@ -267,7 +267,7 @@ class MapCompositionTest { val sndKey = addressSort.mkConst("sndKey") val sndValue = bv32Sort.mkConst("sndValue") - val flatUpdates = UEmptyUpdates, UBv32Sort>( + val flatUpdates = UFlatUpdates, UBv32Sort>( { _, _ -> shouldNotBeCalled() }, { _, _ -> shouldNotBeCalled() }, { _, _ -> shouldNotBeCalled() } @@ -289,15 +289,15 @@ class MapCompositionTest { assertNotSame(illegal = flatUpdates, actual = mappedUpdates) - val node = mappedUpdates.node as UPinpointUpdateNode<*, *> - val next = mappedUpdates.next as UFlatUpdates<*, *> - val nextNode = next.node as UPinpointUpdateNode<*, *> + val node = mappedUpdates.node?.update as UPinpointUpdateNode<*, *> + val next = mappedUpdates.node.next as UFlatUpdates<*, *> + val nextNode = next.node?.update as UPinpointUpdateNode<*, *> assertSame(expected = composedSndKey, actual = node.key) assertSame(expected = composedSndValue, actual = node.value) assertSame(expected = composedFstKey, actual = nextNode.key) assertSame(expected = composedFstValue, actual = nextNode.value) - assertNull(next.next) + assertNull(next.node.next.node) } @Test diff --git a/usvm-core/src/test/kotlin/org/usvm/UpdatesIteratorTest.kt b/usvm-core/src/test/kotlin/org/usvm/UpdatesIteratorTest.kt index b51af057f2..d8fbfa2528 100644 --- a/usvm-core/src/test/kotlin/org/usvm/UpdatesIteratorTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/UpdatesIteratorTest.kt @@ -1,5 +1,6 @@ package org.usvm +import com.microsoft.z3.IntSort import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.usvm.util.SetRegion @@ -35,20 +36,12 @@ class UpdatesIteratorTest { @Test fun testFlatUpdatesIterator() = with(UContext()) { - val firstUpdateNode = UPinpointUpdateNode( - key = 10, - value = 10.toBv(), - keyEqualityComparer = { k1, k2 -> mkEq(k1.toBv(), k2.toBv()) }, - guard = trueExpr - ) - - val flatUpdates = UFlatUpdates( - node = firstUpdateNode, - next = null, + val flatUpdates = UFlatUpdates( { _, _ -> throw NotImplementedError() }, { _, _ -> throw NotImplementedError() }, { _, _ -> throw NotImplementedError() } - ).write(key = 1, value = 1.toBv(), guard = mkTrue()) + ).write(key = 10, value = 10.toBv(), guard = mkTrue()) + .write(key = 1, value = 1.toBv(), guard = mkTrue()) .write(key = 2, value = 2.toBv(), guard = mkTrue()) .write(key = 3, value = 3.toBv(), guard = mkTrue()) From 7b02dfd21809c63cb4b269ee641064129675402e Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Wed, 5 Apr 2023 17:06:45 +0300 Subject: [PATCH 04/28] Draft --- .../main/kotlin/org/usvm/ExprTranslator.kt | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index 432f103c6e..18ab3605a1 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -5,12 +5,6 @@ import org.ksmt.utils.mkConst open class UExprTranslator internal constructor( ctx: UContext, ) : UExprTransformer(ctx) { - private val observers = mutableListOf() - - internal fun attachObserver(observer: UTranslationObserver) { - observers += observer - } - open fun translate(expr: UExpr): UExpr = apply(expr) // TODO: why do we have this function in UExprTransformer? @@ -20,7 +14,6 @@ open class UExprTranslator internal constructor( override fun transform(expr: URegisterReading): UExpr { val registerConst = expr.sort.mkConst("r${expr.idx}") - observers.forEach { it.newRegisterReadingTranslated(expr.idx, registerConst) } return registerConst } @@ -34,7 +27,6 @@ open class UExprTranslator internal constructor( override fun transform(expr: UIndexedMethodReturnValue): UExpr { val const = expr.sort.mkConst("m${expr.method}_${expr.callIndex}") - observers.forEach { it.newIndexedMethodReturnValueTranslated(expr.method, expr.callIndex, const) } return const } @@ -71,7 +63,6 @@ open class UExprTranslator internal constructor( private val regionToTranslator = mutableMapOf, URegionTranslator<*, *, *, *>>() .withDefault { regionId -> val regionTranslator = regionId.translator(this) - observers.forEach { it.newRegionTranslator(regionId, regionTranslator) } regionTranslator } @@ -86,6 +77,19 @@ open class UExprTranslator internal constructor( } } +open class URecordingExprTranslator internal constructor( + ctx: UContext, +) : UExprTranslator(ctx) { + + protected val registerIdxToTranslated: MutableMap> = mutableMapOf() + + override fun transform(expr: URegisterReading): UExpr { + val translated = super.transform(expr) + registerIdxToTranslated[expr.idx] = translated + return translated + } +} + internal typealias RegionTranslatorConstructor = (UExprTranslator<*, *>) -> URegionTranslator, T, U, *> // TODO: maybe split this function into functions of URegionID @@ -118,18 +122,3 @@ internal val URegionId.translator: RegionTranslat } as URegionTranslator, Key, Sort, *> } -// TODO: looks odd, because we duplicate StackEvaluator::eval, MockEvaluator::eval with slightly changed signature... -internal interface UTranslationObserver { - fun newRegionTranslator( - regionId: URegionId<*, *>, - translator: URegionTranslator<*, *, *, *>, - ) - - fun newRegisterReadingTranslated(idx: Int, translated: UExpr) - - fun newIndexedMethodReturnValueTranslated( - method: Method, - callIndex: Int, - translated: UExpr, - ) -} From 16cabd33e8f0db12c89e37d426b13d1e83b461bd Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 5 Apr 2023 18:22:49 +0300 Subject: [PATCH 05/28] First model proposal --- .../main/kotlin/org/usvm/ExprTranslator.kt | 47 ++- usvm-core/src/main/kotlin/org/usvm/Heap.kt | 15 +- usvm-core/src/main/kotlin/org/usvm/Memory.kt | 10 +- usvm-core/src/main/kotlin/org/usvm/Mocks.kt | 20 +- .../main/kotlin/org/usvm/RegionTranslator.kt | 141 +++++++- .../main/kotlin/org/usvm/RegistersStack.kt | 7 - .../src/main/kotlin/org/usvm/TypeStorage.kt | 31 +- .../src/main/kotlin/org/usvm/UModelDecoder.kt | 318 ++++++++++++++++++ 8 files changed, 534 insertions(+), 55 deletions(-) create mode 100644 usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index 18ab3605a1..27da4ed10e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -2,9 +2,15 @@ package org.usvm import org.ksmt.utils.mkConst -open class UExprTranslator internal constructor( +open class UExprTranslator constructor( ctx: UContext, ) : UExprTransformer(ctx) { + private val observers = mutableListOf() + + internal fun attachObserver(observer: UTranslationObserver) { + observers += observer + } + open fun translate(expr: UExpr): UExpr = apply(expr) // TODO: why do we have this function in UExprTransformer? @@ -14,6 +20,7 @@ open class UExprTranslator internal constructor( override fun transform(expr: URegisterReading): UExpr { val registerConst = expr.sort.mkConst("r${expr.idx}") + observers.forEach { it.newRegisterReadingTranslated(expr.idx, registerConst) } return registerConst } @@ -27,10 +34,15 @@ open class UExprTranslator internal constructor( override fun transform(expr: UIndexedMethodReturnValue): UExpr { val const = expr.sort.mkConst("m${expr.method}_${expr.callIndex}") + observers.forEach { it.newIndexedMethodReturnValueTranslated(expr.method, expr.callIndex, const) } return const } - override fun transform(expr: UNullRef): UExpr = expr.sort.mkConst("null") + override fun transform(expr: UNullRef): UExpr { + val const = expr.sort.mkConst("null") + observers.forEach { it.nullRefTranslated(const) } + return const + } override fun transform(expr: UConcreteHeapRef): UExpr { error("Unexpected UConcreteHeapRef $expr in UExprTranslator, that has to be impossible by construction!") @@ -63,6 +75,7 @@ open class UExprTranslator internal constructor( private val regionToTranslator = mutableMapOf, URegionTranslator<*, *, *, *>>() .withDefault { regionId -> val regionTranslator = regionId.translator(this) + observers.forEach { it.newRegionTranslator(regionId, regionTranslator) } regionTranslator } @@ -77,19 +90,6 @@ open class UExprTranslator internal constructor( } } -open class URecordingExprTranslator internal constructor( - ctx: UContext, -) : UExprTranslator(ctx) { - - protected val registerIdxToTranslated: MutableMap> = mutableMapOf() - - override fun transform(expr: URegisterReading): UExpr { - val translated = super.transform(expr) - registerIdxToTranslated[expr.idx] = translated - return translated - } -} - internal typealias RegionTranslatorConstructor = (UExprTranslator<*, *>) -> URegionTranslator, T, U, *> // TODO: maybe split this function into functions of URegionID @@ -122,3 +122,20 @@ internal val URegionId.translator: RegionTranslat } as URegionTranslator, Key, Sort, *> } +// TODO: looks odd, because we duplicate StackEvaluator::eval, MockEvaluator::eval with slightly changed signature... +internal interface UTranslationObserver { + fun newRegionTranslator( + regionId: URegionId<*, *>, + translator: URegionTranslator<*, *, *, *>, + ) + + fun newRegisterReadingTranslated(idx: Int, translated: UExpr) + + fun newIndexedMethodReturnValueTranslated( + method: Method, + callIndex: Int, + translated: UExpr, + ) + + fun nullRefTranslated(translated: UExpr) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/Heap.kt b/usvm-core/src/main/kotlin/org/usvm/Heap.kt index 59ed2a908f..405e600ddd 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Heap.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Heap.kt @@ -1,12 +1,9 @@ package org.usvm -import org.ksmt.solver.KModel -import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue -import org.ksmt.utils.asExpr -import org.ksmt.utils.cast import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.persistentMapOf -import kotlinx.collections.immutable.toPersistentMap +import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue +import org.ksmt.utils.asExpr interface UReadOnlyHeap { fun readField(ref: Ref, field: Field, sort: Sort): Value @@ -80,8 +77,14 @@ typealias USymbolicHeap = UHeap, US * Copying is prohibited. */ class UAddressCounter { - private var lastAddress = 0 + private var lastAddress = INITIAL_CONCRETE_ADDRESS fun freshAddress(): UConcreteHeapAddress = lastAddress++ + + companion object { + const val NULL_ADDRESS = 0 + const val INITIAL_INPUT_ADDRESS = NULL_ADDRESS - 1 + const val INITIAL_CONCRETE_ADDRESS = NULL_ADDRESS + 1 + } } data class URegionHeap( diff --git a/usvm-core/src/main/kotlin/org/usvm/Memory.kt b/usvm-core/src/main/kotlin/org/usvm/Memory.kt index 34553938c6..8e95d89322 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Memory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Memory.kt @@ -60,11 +60,11 @@ typealias USymbolicMemory = UMemory, USizeExpr, @Suppress("MemberVisibilityCanBePrivate") open class UMemoryBase( protected val ctx: UContext, - protected val typeSystem: UTypeSystem, - protected var stack: URegistersStack = URegistersStack(ctx), - protected var heap: USymbolicHeap = URegionHeap(ctx), - protected var types: UTypeStorage = UTypeStorage(ctx, typeSystem), - protected var mocker: UMocker = UIndexedMocker(ctx) + val typeSystem: UTypeSystem, + val stack: URegistersStack = URegistersStack(ctx), + val heap: USymbolicHeap = URegionHeap(ctx), + val types: UTypeStorage = UTypeStorage(ctx, typeSystem), + val mocker: UMocker = UIndexedMocker(ctx) // TODO: we can eliminate mocker by moving compose to UState? ) : USymbolicMemory { @Suppress("UNCHECKED_CAST") diff --git a/usvm-core/src/main/kotlin/org/usvm/Mocks.kt b/usvm-core/src/main/kotlin/org/usvm/Mocks.kt index 184e8c926e..0eb232650c 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Mocks.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Mocks.kt @@ -1,18 +1,30 @@ package org.usvm -import org.ksmt.solver.KModel -import org.ksmt.utils.asExpr import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf +import org.ksmt.solver.KModel +import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue +import org.ksmt.utils.asExpr interface UMockEvaluator { fun eval(symbol: UMockSymbol): UExpr } -class UIndexedMockModel(val map: Map, UExpr>) : UMockEvaluator { - override fun eval(symbol: UMockSymbol): UExpr = map.getValue(symbol).asExpr(symbol.sort) +class UIndexedMockModel( + private val values: Map, UExpr<*>>, +) : UMockEvaluator { + + override fun eval(symbol: UMockSymbol): UExpr { + require(symbol is UIndexedMethodReturnValue<*, Sort>) + + val sort = symbol.sort + @Suppress("UNCHECKED_CAST") + val key = symbol.method as Method to symbol.callIndex + + return values.getOrDefault(key, sort.sampleValue()).asExpr(sort) + } } interface UMocker : UMockEvaluator { diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt index 3d05bf1125..44b2d6a2a5 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt @@ -1,9 +1,15 @@ package org.usvm +import org.ksmt.expr.KArray2Store +import org.ksmt.expr.KArrayConst +import org.ksmt.expr.KArrayStore import org.ksmt.expr.KExpr +import org.ksmt.solver.KModel +import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.sort.KArray2Sort import org.ksmt.sort.KArraySort import org.ksmt.utils.mkConst +import org.usvm.UModelDecoderBase.Companion.mapAddress import org.usvm.util.Region import org.usvm.util.RegionTree import java.util.IdentityHashMap @@ -11,10 +17,10 @@ import java.util.IdentityHashMap /** * [URegionTranslator] defines a template method that translates a region reading to a specific [KExpr] with a sort [Sort]. */ -internal class URegionTranslator, Key, Sort : USort, Result>( +class URegionTranslator, Key, Sort : USort, Result>( private val updateTranslator: UUpdateTranslator, private val updatesTranslator: UUpdatesTranslator, -) { +): UUpdateTranslator by updateTranslator { fun translateReading(region: UMemoryRegion, key: Key): KExpr { val translated = translate(region) return updateTranslator.select(translated, key) @@ -26,9 +32,17 @@ internal class URegionTranslator, Key, Sort : cache.getOrPut(region) { updatesTranslator.translateUpdates(region.updates) } } -internal interface UUpdateTranslator { +interface URegionEvaluator { + fun select(key: Key): UExpr + fun write(key: Key, expr: UExpr) +} + +interface UUpdateTranslator { fun select(result: Result, key: Key): KExpr + // TODO add a comment about connection between evaluators and translators + fun getEvaluator(model: KModel, mapping: Map): URegionEvaluator + fun initialValue(): Result fun applyUpdate(previous: Result, update: UUpdateNode): Result @@ -42,6 +56,11 @@ internal class U1DArrayUpdateTranslator, Sor override fun select(result: KExpr>, key: KExpr): KExpr = result.ctx.mkArraySelect(result, key) + override fun getEvaluator( + model: KModel, + mapping: Map, + ): URegionEvaluator, Sort> = U1DArrayEvaluator(translator = this, model, mapping) + override fun initialValue(): KExpr> { val ctx = regionId.sort.uctx val arraySort = ctx.mkArraySort(keySort, regionId.sort) @@ -89,6 +108,58 @@ internal class U1DArrayUpdateTranslator, Sor } private val KExpr.translated get() = translator.translate(this) + + class U1DArrayEvaluator, Sort>, KeySort : USort, Sort : USort> private constructor() : + URegionEvaluator, Sort> { + + private lateinit var values: MutableMap, UExpr> + private lateinit var constValue: UExpr + + constructor( + translator: U1DArrayUpdateTranslator, + model: KModel, + mapping: Map, + ) : this() { + val initialValue = translator.initialValue() + val evaluatedArray = model.eval(initialValue, isComplete = true) + + var valueCopy = evaluatedArray + + val stores = mutableMapOf, UExpr>() + + while (valueCopy !is KArrayConst<*, *>) { + require(valueCopy is KArrayStore) + + val value = valueCopy.value.mapAddress(mapping) + + val mapAddress = valueCopy.index.mapAddress(mapping) + stores[mapAddress] = value + valueCopy = valueCopy.array + } + @Suppress("UNCHECKED_CAST") + valueCopy as KArrayConst, Sort> + + constValue = valueCopy.value.mapAddress(mapping) + values = stores + } + + constructor( + regionId: RegionId, + mapping: Map, + ) : this() { + val unmappedConstValue = regionId.defaultValue ?: regionId.sort.sampleValue() + + values = mutableMapOf() + // TODO add comment about why we don't need to evaluate this address + constValue = unmappedConstValue.mapAddress(mapping) + } + + override fun select(key: KExpr): UExpr = values.getOrDefault(key, constValue) + + override fun write(key: KExpr, expr: UExpr) { + values[key] = expr + } + } } internal class U2DArrayUpdateTranslator, KExpr>, Sort>, Key1Sort : USort, Key2Sort : USort, Sort : USort>( @@ -103,6 +174,13 @@ internal class U2DArrayUpdateTranslator = result.ctx.mkArraySelect(result, key.first.translated, key.second.translated) + override fun getEvaluator( + model: KModel, + mapping: Map, + ): URegionEvaluator, KExpr>, Sort> { + return U2DArrayEvaluator(translator = this, model, mapping) + } + override fun initialValue(): KExpr> { val ctx = regionId.sort.uctx val arraySort = ctx.mkArraySort(key1Sort, key2Sort, regionId.sort) @@ -153,9 +231,64 @@ internal class U2DArrayUpdateTranslator UExpr.translated get() = translator.translate(this) + + class U2DArrayEvaluator, KExpr>, Sort>, Key1Sort : USort, Key2Sort : USort, Sort : USort> private constructor( + ) : URegionEvaluator, KExpr>, Sort> { + private lateinit var values: MutableMap, UExpr>, UExpr> + private lateinit var constValue: UExpr + + constructor( + translator: U2DArrayUpdateTranslator, + model: KModel, + mapping: Map + ): this() { + val initialValue = translator.initialValue() + val evaluatedArray = model.eval(initialValue, isComplete = true) + + var valueCopy = evaluatedArray + + val stores = mutableMapOf, UExpr>, UExpr>() + + while (valueCopy !is KArrayConst<*, *>) { + require(valueCopy is KArray2Store) + + val value = valueCopy.value.mapAddress(mapping) + + val index0 = valueCopy.index0.mapAddress(mapping) + val index1 = valueCopy.index1.mapAddress(mapping) + + stores[index0 to index1] = value + valueCopy = valueCopy.array + } + + @Suppress("UNCHECKED_CAST") + valueCopy as KArrayConst, Sort> + + constValue = valueCopy.value.mapAddress(mapping) + values = stores + } + + constructor( + regionId: RegionId, + mapping: Map + ): this() { + val unmappedConstValue = regionId.defaultValue ?: regionId.sort.sampleValue() + + values = mutableMapOf() + constValue = unmappedConstValue.mapAddress(mapping) + } + + override fun select(key: Pair, KExpr>): UExpr { + return values.getOrDefault(key, constValue) + } + + override fun write(key: Pair, KExpr>, expr: UExpr) { + values[key] = expr + } + } } -internal interface UUpdatesTranslator { +interface UUpdatesTranslator { fun translateUpdates(updates: UMemoryUpdates): Result } diff --git a/usvm-core/src/main/kotlin/org/usvm/RegistersStack.kt b/usvm-core/src/main/kotlin/org/usvm/RegistersStack.kt index f8b0ce22d4..3b5561efe2 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegistersStack.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegistersStack.kt @@ -62,10 +62,3 @@ class URegistersStack( sort: Sort, ): UExpr = readRegister(registerIndex, sort).asExpr(sort) } - -class URegistersStackModel(private val registers: Array?>) : URegistersStackEvaluator { - override fun eval( - registerIndex: Int, - sort: Sort, - ): UExpr = registers[registerIndex]!!.asExpr(sort) -} \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/TypeStorage.kt b/usvm-core/src/main/kotlin/org/usvm/TypeStorage.kt index 9d0b59ce6f..334c69388b 100644 --- a/usvm-core/src/main/kotlin/org/usvm/TypeStorage.kt +++ b/usvm-core/src/main/kotlin/org/usvm/TypeStorage.kt @@ -4,7 +4,6 @@ import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.PersistentSet import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentSetOf -import java.lang.IllegalArgumentException interface UTypeSystem { // Returns true if t <: u @@ -18,12 +17,10 @@ interface UTypeEvaluator { class UTypeModel( private val ctx: UContext, private val typeSystem: UTypeSystem, - private val map: Map -) - : UTypeEvaluator -{ + private val typeByAddr: Map, +) : UTypeEvaluator { - fun typeOf(address: UConcreteHeapAddress): Type = map.getValue(address) + fun typeOf(address: UConcreteHeapAddress): Type = typeByAddr.getValue(address) override fun evalIs(ref: UHeapRef, type: Type): UBoolExpr = when (ref) { @@ -41,19 +38,17 @@ class UTypeStorage( val typeSystem: UTypeSystem, val isContraditing: Boolean = false, val concreteTypes: PersistentMap = persistentMapOf(), - val supertypes: PersistentMap> = persistentMapOf() -) - : UTypeEvaluator -{ + val supertypes: PersistentMap> = persistentMapOf(), +) : UTypeEvaluator { fun contradiction() = - UTypeStorage(ctx, typeSystem, true, concreteTypes, supertypes) + UTypeStorage(ctx, typeSystem, true, concreteTypes, supertypes) fun allocate(ref: UConcreteHeapAddress, type: Type): UTypeStorage = UTypeStorage(ctx, typeSystem, isContraditing, concreteTypes.put(ref, type), supertypes) fun cast(ref: UHeapRef, type: Type): UTypeStorage { - when(ref) { + when (ref) { is UConcreteHeapRef -> { val concreteType = concreteTypes.getValue(ref.address) if (!typeSystem.isSupertype(type, concreteType)) @@ -61,20 +56,28 @@ class UTypeStorage( else return this } + else -> { val constraints = supertypes.getOrDefault(ref, persistentSetOf()) // TODO: check if we have simple contradiction here - return UTypeStorage(ctx, typeSystem, isContraditing, concreteTypes, supertypes.put(ref, constraints.add(type))) + return UTypeStorage( + ctx, + typeSystem, + isContraditing, + concreteTypes, + supertypes.put(ref, constraints.add(type)) + ) } } } override fun evalIs(ref: UHeapRef, type: Type): UBoolExpr { - when(ref) { + when (ref) { is UConcreteHeapRef -> { val concreteType = concreteTypes.getValue(ref.address) return if (typeSystem.isSupertype(type, concreteType)) ctx.trueExpr else ctx.falseExpr } + else -> { @Suppress("UNUSED_VARIABLE") val constraints = supertypes.getOrDefault(ref, persistentSetOf()) diff --git a/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt new file mode 100644 index 0000000000..84ae9c8c26 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt @@ -0,0 +1,318 @@ +package org.usvm + +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentMapOf +import org.ksmt.expr.KExpr +import org.ksmt.expr.KInterpretedValue +import org.ksmt.solver.KModel +import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue +import org.ksmt.sort.KUninterpretedSort +import org.ksmt.utils.asExpr +import org.usvm.UAddressCounter.Companion.INITIAL_INPUT_ADDRESS +import org.usvm.UAddressCounter.Companion.NULL_ADDRESS + +interface UModelDecoder { + fun decode(memory: Memory, model: KModel): Model +} + +class UModelDecoderBase( + translator: UExprTranslator, +) : UModelDecoder, UModelBase>, UTranslationObserver { + + private val ctx: UContext = translator.ctx as UContext + + init { + translator.attachObserver(this) + } + + private val regionToTranslator = mutableMapOf, URegionTranslator<*, *, *, *>>() + private val registerIdxToTranslated = mutableMapOf>() + private val indexedMethodReturnValueToTranslated = mutableMapOf, UExpr<*>>() + private lateinit var nullRef: UExpr + + override fun newRegionTranslator(regionId: URegionId<*, *>, translator: URegionTranslator<*, *, *, *>) { + regionToTranslator[regionId] = translator + } + + override fun newRegisterReadingTranslated(idx: Int, translated: UExpr) { + registerIdxToTranslated[idx] = translated + } + + override fun newIndexedMethodReturnValueTranslated( + method: Method, + callIndex: Int, + translated: UExpr, + ) { + indexedMethodReturnValueToTranslated[method to callIndex] = translated + } + + override fun nullRefTranslated(translated: UExpr) { + nullRef = translated + } + + private fun buildMapping(model: KModel): Map, UConcreteHeapRef> { + val interpretedNullRef = model.eval(nullRef, isComplete = true) + + val universe = model.uninterpretedSortUniverse(ctx.addressSort) ?: return emptyMap() + var counter = INITIAL_INPUT_ADDRESS + + val result = mutableMapOf, UConcreteHeapRef>() + result[ctx.nullRef] = ctx.mkConcreteHeapRef(NULL_ADDRESS) + + for (interpretedAddress in universe ) { + if (universe == interpretedNullRef) { + continue + } + result[interpretedAddress] = ctx.mkConcreteHeapRef(counter--) + } + return result + } + + override fun decode( + memory: UMemoryBase, + model: KModel, + ): UModelBase { + val mapping = buildMapping(model) + + val stack = decodeStack(model, mapping) + val heap = decodeHeap(model, mapping) + val types = UTypeModel(ctx, memory.typeSystem, typeByAddr = emptyMap()) + val mocks = decodeMocker(model, mapping) + + return UModelBase(ctx, stack, heap, types, mocks) + } + + @Suppress("UNCHECKED_CAST", "SafeCastWithReturn") + private fun decodeHeap( + model: KModel, + mapping: Map, UConcreteHeapRef>, + ): UHeapModel { + var inputFields = persistentMapOf>() + var inputArrays = persistentMapOf, out USort>>() + var inputLengths = persistentMapOf>() + + regionToTranslator.forEach { (regionId, translator) -> + when (regionId) { + is UInputFieldRegionId<*, *> -> { + regionId as? UInputFieldRegionId ?: return@forEach + + val evaluator = translator.getEvaluator(model, mapping) as URegionEvaluator + inputFields = inputFields.put(regionId.field, evaluator) + } + + is UInputArrayId<*, *> -> { + regionId as? UInputArrayId ?: return@forEach + + val evaluator = translator.getEvaluator( + model, + mapping + ) as URegionEvaluator, out USort> + inputArrays = inputArrays.put(regionId.arrayType, evaluator) + } + + is UInputArrayLengthId<*> -> { + regionId as? UInputArrayLengthId ?: return@forEach + + val evaluator = translator.getEvaluator(model, mapping) as URegionEvaluator + inputLengths = inputLengths.put(regionId.arrayType, evaluator) + } + } + } + + return UHeapModel(ctx, inputFields, inputArrays, inputLengths, mapping) + } + + private fun decodeStack(model: KModel, mapping: Map, UConcreteHeapRef>): URegistersStackModel { + val registers = registerIdxToTranslated.replaceUninterpretedConsts(model, mapping) + + return URegistersStackModel(registers) + } + + private fun decodeMocker( + model: KModel, + mapping: Map, UConcreteHeapRef>, + ): UIndexedMockModel { + val values = indexedMethodReturnValueToTranslated.replaceUninterpretedConsts(model, mapping) + + return UIndexedMockModel(values) + } + + private fun Map>.replaceUninterpretedConsts( + model: KModel, + mapping: Map, UConcreteHeapRef>, + ): Map> { + val values = mapValues { (_, expr) -> + val value = model.eval(expr, isComplete = true) + val transformedValue = value.mapAddress(mapping) + + transformedValue + } + + return values + } + + companion object { + // TODO add docs + fun UExpr.mapAddress( + mapping: Map, UConcreteHeapRef>, + ): UExpr = if (sort == uctx.addressSort) { + mapping.getValue(asExpr(uctx.addressSort)).asExpr(sort) + } else { + this + } + + } +} + +class URegistersStackModel(private val registers: Map>) : URegistersStackEvaluator { + override fun eval( + registerIndex: Int, + sort: Sort, + ): UExpr = registers.getOrDefault(registerIndex, sort.sampleValue()).asExpr(sort) +} + +class UHeapModel( + private val ctx: UContext, + private var resolvedInputFields: PersistentMap>, + private var resolvedInputArrays: PersistentMap, out USort>>, + private var resolvedInputLengths: PersistentMap>, + private val uninterpretedAddressesMapping: Map, +) : USymbolicHeap { + override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr { + // TODO add a comment about it + require(ref is UConcreteHeapRef && ref.address < INPUT_ADDRESSES_THRESHOLD) + + return resolvedInputFields[field]?.select(ref)?.asExpr(sort) ?: sort.sampleValue() + } + + override fun readArrayIndex( + ref: UHeapRef, + index: USizeExpr, + arrayType: ArrayType, + elementSort: Sort, + ): UExpr { + // TODO add a comment about it + require(ref is UConcreteHeapRef && ref.address < INPUT_ADDRESSES_THRESHOLD) + + val key = ref to index + return resolvedInputArrays[arrayType]?.select(key)?.asExpr(elementSort) ?: elementSort.sampleValue() + } + + override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType): USizeExpr { + require(ref is UConcreteHeapRef && ref.address < INPUT_ADDRESSES_THRESHOLD) + + return resolvedInputLengths[arrayType]?.select(ref) ?: ctx.sizeSort.sampleValue() + } + + override fun writeField( + ref: UHeapRef, + field: Field, + sort: Sort, + value: UExpr, + guard: UBoolExpr, + ) { + when { + guard.isFalse -> return + else -> require(guard.isTrue) + } + + val valueToWrite = value.asExpr(sort) + + require(ref is UConcreteHeapRef && ref.address < INPUT_ADDRESSES_THRESHOLD) + + @Suppress("UNCHECKED_CAST") + val regionEvaluator = resolvedInputFields.getOrElse(field) { + val regionId = UInputFieldRegionId(field, sort) + val result = U1DArrayUpdateTranslator.U1DArrayEvaluator(regionId, uninterpretedAddressesMapping) + + resolvedInputFields = resolvedInputFields.put(field, result) + result + } as URegionEvaluator + + regionEvaluator.write(ref, valueToWrite) + } + + override fun writeArrayIndex( + ref: UHeapRef, + index: USizeExpr, + type: ArrayType, + elementSort: Sort, + value: UExpr, + guard: UBoolExpr, + ) { + when { + guard.isFalse -> return + else -> require(guard.isTrue) + } + + require(index is KInterpretedValue) + require(ref is UConcreteHeapRef && ref.address < INPUT_ADDRESSES_THRESHOLD) + + val valueToWrite = value.asExpr(elementSort) + + @Suppress("UNCHECKED_CAST") + val regionEvaluator = resolvedInputArrays.getOrElse(type) { + val regionId = UInputArrayId(type, elementSort) + val result = U2DArrayUpdateTranslator.U2DArrayEvaluator(regionId, uninterpretedAddressesMapping) + + resolvedInputArrays = resolvedInputArrays.put(type, result) + result + } as URegionEvaluator, Sort> + + regionEvaluator.write(ref to index, valueToWrite) + } + + override fun writeArrayLength(ref: UHeapRef, size: USizeExpr, arrayType: ArrayType) { + require(size is KInterpretedValue) + require(ref is UConcreteHeapRef && ref.address < INPUT_ADDRESSES_THRESHOLD) + + resolvedInputLengths.getOrElse(arrayType) { + val regionId = UInputArrayLengthId(arrayType, ctx.sizeSort) + val result = U1DArrayUpdateTranslator.U1DArrayEvaluator(regionId, uninterpretedAddressesMapping) + + resolvedInputLengths = resolvedInputLengths.put(arrayType, result) + result + }.write(ref, size) + } + + override fun memcpy( + srcRef: UHeapRef, + dstRef: UHeapRef, + type: ArrayType, + elementSort: Sort, + fromSrcIdx: USizeExpr, + fromDstIdx: USizeExpr, + toDstIdx: USizeExpr, + guard: UBoolExpr, + ) { + TODO("Not yet implemented") + } + + override fun memset( + ref: UHeapRef, + type: ArrayType, + sort: Sort, + contents: Sequence>, + ) { + TODO("Not yet implemented") + } + + override fun allocate() = error("Illegal operation for a model") + + override fun allocateArray(count: USizeExpr): UConcreteHeapAddress = error("Illegal operation for a model") + + override fun clone(): UHeap, USizeExpr, Field, ArrayType, UBoolExpr> = + UHeapModel( + ctx, + resolvedInputFields, + resolvedInputArrays, + resolvedInputLengths, + uninterpretedAddressesMapping + ) + + override fun toMutableHeap() = clone() + + companion object { + const val INPUT_ADDRESSES_THRESHOLD = 0 + } +} \ No newline at end of file From 90a64d458264e0c380550e0e66fff1557d9f6fd7 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 6 Apr 2023 18:27:48 +0300 Subject: [PATCH 06/28] Docs and small refactorings --- usvm-core/src/main/kotlin/org/usvm/Heap.kt | 4 + usvm-core/src/main/kotlin/org/usvm/Mocks.kt | 4 + .../main/kotlin/org/usvm/RegionTranslator.kt | 128 ++++++++++++------ .../src/main/kotlin/org/usvm/UModelDecoder.kt | 107 ++++++++++----- 4 files changed, 168 insertions(+), 75 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/Heap.kt b/usvm-core/src/main/kotlin/org/usvm/Heap.kt index 405e600ddd..b0daf73c99 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Heap.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Heap.kt @@ -81,6 +81,10 @@ class UAddressCounter { fun freshAddress(): UConcreteHeapAddress = lastAddress++ companion object { + // We split all addresses into three parts: + // * input values: [Int.MIN_VALUE..0), + // * null value: [0] + // * allocated values: (0..Int.MAX_VALUE] const val NULL_ADDRESS = 0 const val INITIAL_INPUT_ADDRESS = NULL_ADDRESS - 1 const val INITIAL_CONCRETE_ADDRESS = NULL_ADDRESS + 1 diff --git a/usvm-core/src/main/kotlin/org/usvm/Mocks.kt b/usvm-core/src/main/kotlin/org/usvm/Mocks.kt index 0eb232650c..b17eabc9dd 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Mocks.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Mocks.kt @@ -12,6 +12,10 @@ interface UMockEvaluator { fun eval(symbol: UMockSymbol): UExpr } +/** + * A model for an indexed mocker that stores mapping + * from mock symbols and invocation indices to expressions. + */ class UIndexedMockModel( private val values: Map, UExpr<*>>, ) : UMockEvaluator { diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt index 44b2d6a2a5..c83cd8ec7e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt @@ -3,7 +3,6 @@ package org.usvm import org.ksmt.expr.KArray2Store import org.ksmt.expr.KArrayConst import org.ksmt.expr.KArrayStore -import org.ksmt.expr.KExpr import org.ksmt.solver.KModel import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.sort.KArray2Sort @@ -15,13 +14,13 @@ import org.usvm.util.RegionTree import java.util.IdentityHashMap /** - * [URegionTranslator] defines a template method that translates a region reading to a specific [KExpr] with a sort [Sort]. + * [URegionTranslator] defines a template method that translates a region reading to a specific [UExpr] with a sort [Sort]. */ class URegionTranslator, Key, Sort : USort, Result>( private val updateTranslator: UUpdateTranslator, private val updatesTranslator: UUpdatesTranslator, -): UUpdateTranslator by updateTranslator { - fun translateReading(region: UMemoryRegion, key: Key): KExpr { +) : UUpdateTranslator by updateTranslator { + fun translateReading(region: UMemoryRegion, key: Key): UExpr { val translated = translate(region) return updateTranslator.select(translated, key) } @@ -38,9 +37,16 @@ interface URegionEvaluator { } interface UUpdateTranslator { - fun select(result: Result, key: Key): KExpr - - // TODO add a comment about connection between evaluators and translators + fun select(result: Result, key: Key): UExpr + + /** + * Returns an evaluator for the current translator. + * Note that it is a translator-specific evaluator that + * knows how to decode values from the [model]. + * + * [mapping] contains information about matching expressions of the + * address sort and their concrete (evaluated) representations. + */ fun getEvaluator(model: KModel, mapping: Map): URegionEvaluator fun initialValue(): Result @@ -52,16 +58,16 @@ internal class U1DArrayUpdateTranslator, Sor private val translator: UExprTranslator<*, *>, private val keySort: KeySort, private val regionId: RegionId, -) : UUpdateTranslator, Sort, KExpr>> { - override fun select(result: KExpr>, key: KExpr): KExpr = +) : UUpdateTranslator, Sort, UExpr>> { + override fun select(result: UExpr>, key: UExpr): UExpr = result.ctx.mkArraySelect(result, key) override fun getEvaluator( model: KModel, mapping: Map, - ): URegionEvaluator, Sort> = U1DArrayEvaluator(translator = this, model, mapping) + ): URegionEvaluator, Sort> = U1DArrayEvaluator(translator = this, model, mapping) - override fun initialValue(): KExpr> { + override fun initialValue(): UExpr> { val ctx = regionId.sort.uctx val arraySort = ctx.mkArraySort(keySort, regionId.sort) val defaultValue = regionId.defaultValue @@ -74,9 +80,9 @@ internal class U1DArrayUpdateTranslator, Sor } override fun applyUpdate( - previous: KExpr>, - update: UUpdateNode, Sort>, - ): KExpr> = with(previous.uctx) { + previous: UExpr>, + update: UUpdateNode, Sort>, + ): UExpr> = with(previous.uctx) { when (update) { is UPinpointUpdateNode -> { val key = update.key.translated @@ -107,19 +113,32 @@ internal class U1DArrayUpdateTranslator, Sor } } - private val KExpr.translated get() = translator.translate(this) + private val UExpr.translated get() = translator.translate(this) + /** + * A specific evaluator for one-dimensional regions generalized by a single expression of a [KeySort]. + */ class U1DArrayEvaluator, Sort>, KeySort : USort, Sort : USort> private constructor() : - URegionEvaluator, Sort> { + URegionEvaluator, Sort> { private lateinit var values: MutableMap, UExpr> private lateinit var constValue: UExpr + /** + * A constructor that is used in regular cases for a region + * that has a corresponding translator. It collects information + * required for the region decoding using data about translated expressions, + * resolved values from the [model] and the [mapping] from address expressions + * to their concrete representation. + */ constructor( translator: U1DArrayUpdateTranslator, model: KModel, mapping: Map, ) : this() { + // Since the model contains only information about values we got from the outside, + // we can translate and ask only about an initial value for the region. + // All other values should be resolved earlier without asking the model. val initialValue = translator.initialValue() val evaluatedArray = model.eval(initialValue, isComplete = true) @@ -127,6 +146,7 @@ internal class U1DArrayUpdateTranslator, Sor val stores = mutableMapOf, UExpr>() + // Parse stores into the region, then collect a const value for the evaluated region. while (valueCopy !is KArrayConst<*, *>) { require(valueCopy is KArrayStore) @@ -143,45 +163,54 @@ internal class U1DArrayUpdateTranslator, Sor values = stores } + /** + * A constructor that is used in cases when we try to evaluate + * an expression from a region that was never translated. + */ constructor( regionId: RegionId, mapping: Map, ) : this() { - val unmappedConstValue = regionId.defaultValue ?: regionId.sort.sampleValue() + // If some region has a default value, it means that the region is an allocated one. + // All such regions must be processed earlier, and we won't have them here. + require(regionId.defaultValue == null) + + // So, for these region we should take sample values for theis sorts. + val unmappedConstValue = regionId.sort.sampleValue() values = mutableMapOf() - // TODO add comment about why we don't need to evaluate this address + // Because of this, we can get rid of evaluation and return a possibly mapped interpreted value. constValue = unmappedConstValue.mapAddress(mapping) } - override fun select(key: KExpr): UExpr = values.getOrDefault(key, constValue) + override fun select(key: UExpr): UExpr = values.getOrDefault(key, constValue) - override fun write(key: KExpr, expr: UExpr) { + override fun write(key: UExpr, expr: UExpr) { values[key] = expr } } } -internal class U2DArrayUpdateTranslator, KExpr>, Sort>, Key1Sort : USort, Key2Sort : USort, Sort : USort>( +internal class U2DArrayUpdateTranslator, UExpr>, Sort>, Key1Sort : USort, Key2Sort : USort, Sort : USort>( private val translator: UExprTranslator<*, *>, private val key1Sort: Key1Sort, private val key2Sort: Key2Sort, private val regionId: RegionId, -) : UUpdateTranslator, KExpr>, Sort, KExpr>> { +) : UUpdateTranslator, UExpr>, Sort, UExpr>> { override fun select( - result: KExpr>, - key: Pair, KExpr>, - ): KExpr = + result: UExpr>, + key: Pair, UExpr>, + ): UExpr = result.ctx.mkArraySelect(result, key.first.translated, key.second.translated) override fun getEvaluator( model: KModel, mapping: Map, - ): URegionEvaluator, KExpr>, Sort> { + ): URegionEvaluator, UExpr>, Sort> { return U2DArrayEvaluator(translator = this, model, mapping) } - override fun initialValue(): KExpr> { + override fun initialValue(): UExpr> { val ctx = regionId.sort.uctx val arraySort = ctx.mkArraySort(key1Sort, key2Sort, regionId.sort) @@ -196,9 +225,9 @@ internal class U2DArrayUpdateTranslator>, - update: UUpdateNode, KExpr>, Sort>, - ): KExpr> = with(previous.uctx) { + previous: UExpr>, + update: UUpdateNode, UExpr>, Sort>, + ): UExpr> = with(previous.uctx) { when (update) { is UPinpointUpdateNode -> { val key1 = update.key.first.translated @@ -232,16 +261,27 @@ internal class U2DArrayUpdateTranslator UExpr.translated get() = translator.translate(this) - class U2DArrayEvaluator, KExpr>, Sort>, Key1Sort : USort, Key2Sort : USort, Sort : USort> private constructor( - ) : URegionEvaluator, KExpr>, Sort> { + /** + * A specific evaluator for two-dimensional regions generalized be a pair + * of two expressions with [Key1Sort] and [Key2Sort] sorts. + */ + class U2DArrayEvaluator, UExpr>, Sort>, Key1Sort : USort, Key2Sort : USort, Sort : USort> private constructor( + ) : URegionEvaluator, UExpr>, Sort> { private lateinit var values: MutableMap, UExpr>, UExpr> private lateinit var constValue: UExpr + /** + * A constructor that is used in regular cases for a region + * that has a corresponding translator. It collects information + * required for the region decoding using data about translated expressions, + * resolved values from the [model] and the [mapping] from address expressions + * to their concrete representation. + */ constructor( translator: U2DArrayUpdateTranslator, model: KModel, - mapping: Map - ): this() { + mapping: Map, + ) : this() { val initialValue = translator.initialValue() val evaluatedArray = model.eval(initialValue, isComplete = true) @@ -268,21 +308,31 @@ internal class U2DArrayUpdateTranslator - ): this() { - val unmappedConstValue = regionId.defaultValue ?: regionId.sort.sampleValue() + mapping: Map, + ) : this() { + // If some region has a default value, it means that the region is an allocated one. + // All such regions must be processed earlier, and we won't have them here. + require(regionId.defaultValue == null) + + // So, for these region we should take sample values for theis sorts. + val unmappedConstValue = regionId.sort.sampleValue() values = mutableMapOf() + // Because of this, we can get rid of evaluation and return a possibly mapped interpreted value. constValue = unmappedConstValue.mapAddress(mapping) } - override fun select(key: Pair, KExpr>): UExpr { + override fun select(key: Pair, UExpr>): UExpr { return values.getOrDefault(key, constValue) } - override fun write(key: Pair, KExpr>, expr: UExpr) { + override fun write(key: Pair, UExpr>, expr: UExpr) { values[key] = expr } } diff --git a/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt index 84ae9c8c26..11bf4c1449 100644 --- a/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt @@ -50,42 +50,40 @@ class UModelDecoderBase( nullRef = translated } + /** + * Build a mapping from instances of an uninterpreted [UAddressSort] + * to [UConcreteHeapRef] with integer addresses. It allows us to enumerate + * equivalence classes of addresses and work with their number in the future. + */ private fun buildMapping(model: KModel): Map, UConcreteHeapRef> { + // Null is a special value that we want to translate in any case. val interpretedNullRef = model.eval(nullRef, isComplete = true) val universe = model.uninterpretedSortUniverse(ctx.addressSort) ?: return emptyMap() + // All the numbers are enumerated from the INITIAL_INPUT_ADDRESS to the Int.MIN_VALUE var counter = INITIAL_INPUT_ADDRESS val result = mutableMapOf, UConcreteHeapRef>() + // Except the null value, it has the NULL_ADDRESS result[ctx.nullRef] = ctx.mkConcreteHeapRef(NULL_ADDRESS) - for (interpretedAddress in universe ) { + for (interpretedAddress in universe) { if (universe == interpretedNullRef) { continue } result[interpretedAddress] = ctx.mkConcreteHeapRef(counter--) } - return result - } - - override fun decode( - memory: UMemoryBase, - model: KModel, - ): UModelBase { - val mapping = buildMapping(model) - val stack = decodeStack(model, mapping) - val heap = decodeHeap(model, mapping) - val types = UTypeModel(ctx, memory.typeSystem, typeByAddr = emptyMap()) - val mocks = decodeMocker(model, mapping) - - return UModelBase(ctx, stack, heap, types, mocks) + return result } + /** + * Constructs a [UHeapModel] for a heap by provided [model] and [addressesMapping]. + */ @Suppress("UNCHECKED_CAST", "SafeCastWithReturn") private fun decodeHeap( model: KModel, - mapping: Map, UConcreteHeapRef>, + addressesMapping: Map, UConcreteHeapRef>, ): UHeapModel { var inputFields = persistentMapOf>() var inputArrays = persistentMapOf, out USort>>() @@ -96,7 +94,7 @@ class UModelDecoderBase( is UInputFieldRegionId<*, *> -> { regionId as? UInputFieldRegionId ?: return@forEach - val evaluator = translator.getEvaluator(model, mapping) as URegionEvaluator + val evaluator = translator.getEvaluator(model, addressesMapping) as URegionEvaluator inputFields = inputFields.put(regionId.field, evaluator) } @@ -105,7 +103,7 @@ class UModelDecoderBase( val evaluator = translator.getEvaluator( model, - mapping + addressesMapping ) as URegionEvaluator, out USort> inputArrays = inputArrays.put(regionId.arrayType, evaluator) } @@ -113,13 +111,28 @@ class UModelDecoderBase( is UInputArrayLengthId<*> -> { regionId as? UInputArrayLengthId ?: return@forEach - val evaluator = translator.getEvaluator(model, mapping) as URegionEvaluator + val evaluator = + translator.getEvaluator(model, addressesMapping) as URegionEvaluator inputLengths = inputLengths.put(regionId.arrayType, evaluator) } } } - return UHeapModel(ctx, inputFields, inputArrays, inputLengths, mapping) + return UHeapModel(ctx, inputFields, inputArrays, inputLengths, addressesMapping) + } + + override fun decode( + memory: UMemoryBase, + model: KModel, + ): UModelBase { + val addressesMapping = buildMapping(model) + + val stack = decodeStack(model, addressesMapping) + val heap = decodeHeap(model, addressesMapping) + val types = UTypeModel(ctx, memory.typeSystem, typeByAddr = emptyMap()) + val mocks = decodeMocker(model, addressesMapping) + + return UModelBase(ctx, stack, heap, types, mocks) } private fun decodeStack(model: KModel, mapping: Map, UConcreteHeapRef>): URegistersStackModel { @@ -137,13 +150,18 @@ class UModelDecoderBase( return UIndexedMockModel(values) } + /** + * Since expressions from [this] might have the [UAddressSort] and therefore + * they could be uninterpreted constants, we have to replace them with + * corresponding concrete addresses from the [addressesMapping]. + */ private fun Map>.replaceUninterpretedConsts( model: KModel, - mapping: Map, UConcreteHeapRef>, + addressesMapping: Map, UConcreteHeapRef>, ): Map> { val values = mapValues { (_, expr) -> val value = model.eval(expr, isComplete = true) - val transformedValue = value.mapAddress(mapping) + val transformedValue = value.mapAddress(addressesMapping) transformedValue } @@ -152,7 +170,11 @@ class UModelDecoderBase( } companion object { - // TODO add docs + /** + * If [this] value is an instance of address expression, returns + * an expression with a corresponding concrete address, otherwise + * returns [this] unchanched. + */ fun UExpr.mapAddress( mapping: Map, UConcreteHeapRef>, ): UExpr = if (sort == uctx.addressSort) { @@ -160,7 +182,6 @@ class UModelDecoderBase( } else { this } - } } @@ -179,8 +200,10 @@ class UHeapModel( private val uninterpretedAddressesMapping: Map, ) : USymbolicHeap { override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr { - // TODO add a comment about it - require(ref is UConcreteHeapRef && ref.address < INPUT_ADDRESSES_THRESHOLD) + // All the expressions in the model are interpreted, therefore, they must + // have concrete addresses. Moreover, the model known only about input values + // which have addresses less or equal than INITIAL_INPUT_ADDRESS + require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) return resolvedInputFields[field]?.select(ref)?.asExpr(sort) ?: sort.sampleValue() } @@ -191,15 +214,20 @@ class UHeapModel( arrayType: ArrayType, elementSort: Sort, ): UExpr { - // TODO add a comment about it - require(ref is UConcreteHeapRef && ref.address < INPUT_ADDRESSES_THRESHOLD) + // All the expressions in the model are interpreted, therefore, they must + // have concrete addresses. Moreover, the model known only about input values + // which have addresses less or equal than INITIAL_INPUT_ADDRESS + require(ref is UConcreteHeapRef && ref.address < INITIAL_INPUT_ADDRESS) val key = ref to index return resolvedInputArrays[arrayType]?.select(key)?.asExpr(elementSort) ?: elementSort.sampleValue() } override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType): USizeExpr { - require(ref is UConcreteHeapRef && ref.address < INPUT_ADDRESSES_THRESHOLD) + // All the expressions in the model are interpreted, therefore, they must + // have concrete addresses. Moreover, the model known only about input values + // which have addresses less or equal than INITIAL_INPUT_ADDRESS + require(ref is UConcreteHeapRef && ref.address < INITIAL_INPUT_ADDRESS) return resolvedInputLengths[arrayType]?.select(ref) ?: ctx.sizeSort.sampleValue() } @@ -211,6 +239,7 @@ class UHeapModel( value: UExpr, guard: UBoolExpr, ) { + // Since all values in the model are interpreted, we can check the exact guard value. when { guard.isFalse -> return else -> require(guard.isTrue) @@ -218,7 +247,10 @@ class UHeapModel( val valueToWrite = value.asExpr(sort) - require(ref is UConcreteHeapRef && ref.address < INPUT_ADDRESSES_THRESHOLD) + // All the expressions in the model are interpreted, therefore, they must + // have concrete addresses. Moreover, the model known only about input values + // which have addresses less or equal than INITIAL_INPUT_ADDRESS + require(ref is UConcreteHeapRef && ref.address < INITIAL_INPUT_ADDRESS) @Suppress("UNCHECKED_CAST") val regionEvaluator = resolvedInputFields.getOrElse(field) { @@ -240,13 +272,17 @@ class UHeapModel( value: UExpr, guard: UBoolExpr, ) { + // Since all values in the model are interpreted, we can check the exact guard value. when { guard.isFalse -> return else -> require(guard.isTrue) } + // All the expressions in the model are interpreted, therefore, they must + // have concrete addresses. Moreover, the model known only about input values + // which have addresses less or equal than INITIAL_INPUT_ADDRESS require(index is KInterpretedValue) - require(ref is UConcreteHeapRef && ref.address < INPUT_ADDRESSES_THRESHOLD) + require(ref is UConcreteHeapRef && ref.address < INITIAL_INPUT_ADDRESS) val valueToWrite = value.asExpr(elementSort) @@ -263,8 +299,11 @@ class UHeapModel( } override fun writeArrayLength(ref: UHeapRef, size: USizeExpr, arrayType: ArrayType) { + // All the expressions in the model are interpreted, therefore, they must + // have concrete addresses. Moreover, the model known only about input values + // which have addresses less or equal than INITIAL_INPUT_ADDRESS require(size is KInterpretedValue) - require(ref is UConcreteHeapRef && ref.address < INPUT_ADDRESSES_THRESHOLD) + require(ref is UConcreteHeapRef && ref.address < INITIAL_INPUT_ADDRESS) resolvedInputLengths.getOrElse(arrayType) { val regionId = UInputArrayLengthId(arrayType, ctx.sizeSort) @@ -311,8 +350,4 @@ class UHeapModel( ) override fun toMutableHeap() = clone() - - companion object { - const val INPUT_ADDRESSES_THRESHOLD = 0 - } } \ No newline at end of file From e0164b2b78294bec40d73d82ff6f28e11fb0a432 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 10 Apr 2023 13:09:45 +0300 Subject: [PATCH 07/28] Refactorings --- buildSrc/src/main/kotlin/Versions.kt | 2 +- .../src/main/kotlin/org/usvm/Composition.kt | 25 +- usvm-core/src/main/kotlin/org/usvm/Context.kt | 2 +- .../main/kotlin/org/usvm/ExprTranslator.kt | 158 ++++++----- .../src/main/kotlin/org/usvm/Expressions.kt | 2 +- usvm-core/src/main/kotlin/org/usvm/Heap.kt | 6 + .../kotlin/org/usvm/HeapRefSplittingUtil.kt | 14 +- .../src/main/kotlin/org/usvm/MemoryRegions.kt | 13 +- usvm-core/src/main/kotlin/org/usvm/Mocks.kt | 6 - usvm-core/src/main/kotlin/org/usvm/Model.kt | 14 +- .../main/kotlin/org/usvm/RegionEvaluator.kt | 196 ++++++++++++++ .../src/main/kotlin/org/usvm/RegionIds.kt | 44 ++- .../main/kotlin/org/usvm/RegionTranslator.kt | 251 ++---------------- usvm-core/src/main/kotlin/org/usvm/Solver.kt | 61 +++++ .../main/kotlin/org/usvm/TranslatorBuilder.kt | 109 ++++++++ .../src/main/kotlin/org/usvm/UModelDecoder.kt | 172 +++++------- .../src/main/kotlin/org/usvm/UpdateNodes.kt | 4 +- .../test/kotlin/org/usvm/CompositionTest.kt | 4 +- .../test/kotlin/org/usvm/ModelDecodingTest.kt | 67 +++++ .../src/test/kotlin/org/usvm/TestUtil.kt | 1 + .../test/kotlin/org/usvm/TranslationTest.kt | 8 +- 21 files changed, 713 insertions(+), 446 deletions(-) create mode 100644 usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt create mode 100644 usvm-core/src/main/kotlin/org/usvm/Solver.kt create mode 100644 usvm-core/src/main/kotlin/org/usvm/TranslatorBuilder.kt create mode 100644 usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 8e56b00e90..c114331326 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -1,5 +1,5 @@ object Versions { - const val ksmt = "0.4.4" + const val ksmt = "0.4.6" const val collections = "0.3.5" const val coroutines = "1.6.4" const val jcdb = "a5c479bcf8" diff --git a/usvm-core/src/main/kotlin/org/usvm/Composition.kt b/usvm-core/src/main/kotlin/org/usvm/Composition.kt index 9cdcf05840..ac74721efb 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Composition.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Composition.kt @@ -36,15 +36,22 @@ open class UComposer( expr: UHeapReading, key: Key ): UExpr = with(expr) { - val instantiator = { key: Key, memoryRegion: UMemoryRegion -> - // Create a copy of this heap to avoid its modification - val heapToApplyUpdates = heapEvaluator.toMutableHeap() - memoryRegion.applyTo(heapToApplyUpdates) - region.regionId.read(heapToApplyUpdates, key) + // if region.defaultValue != null, we don't need to apply updates to the heapEvaluator. expr.region + // already contains ALL region writes, and underlying value (defaultValue) is defined, so we have all the + // required information, and it cannot be refined. + // Otherwise, the underlying value may be reified accordingly to the heapEvaluator + val mappedRegion = if (region.defaultValue == null) { + val instantiator = { key: Key, memoryRegion: UMemoryRegion -> + // Create a copy of this heap to avoid its modification + val heapToApplyUpdates = heapEvaluator.toMutableHeap() + memoryRegion.applyTo(heapToApplyUpdates) + region.regionId.read(heapToApplyUpdates, key) + } + region.map(this@UComposer, instantiator) + } else { + region.map(this@UComposer) } - - val mappedRegion = region.map(this@UComposer, instantiator) - val mappedKey = region.regionId.keyMapper(this@UComposer)(key) + val mappedKey = mappedRegion.regionId.keyMapper(this@UComposer)(key) mappedRegion.read(mappedKey) } @@ -62,5 +69,5 @@ open class UComposer( override fun transform(expr: UConcreteHeapRef): UExpr = expr - override fun transform(expr: UNullRef): UNullRef = expr + override fun transform(expr: UNullRef): UExpr = heapEvaluator.nullRef() } diff --git a/usvm-core/src/main/kotlin/org/usvm/Context.kt b/usvm-core/src/main/kotlin/org/usvm/Context.kt index fec806f63e..6f87c6f452 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Context.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Context.kt @@ -47,7 +47,7 @@ open class UContext( fun mkHeapRefEq(lhs: UHeapRef, rhs: UHeapRef): UBoolExpr = when { // fast checks - lhs is USymbolicHeapRef && rhs is USymbolicHeapRef -> super.mkEq(lhs, rhs) + lhs is USymbolicHeapRef && rhs is USymbolicHeapRef -> super.mkEq(lhs, rhs, order = true) lhs is UConcreteHeapRef && rhs is UConcreteHeapRef -> mkBool(lhs == rhs) // unfolding else -> { diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index 27da4ed10e..d09937527a 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -1,15 +1,24 @@ package org.usvm +import org.ksmt.sort.KArray2Sort +import org.ksmt.sort.KArraySort +import org.ksmt.sort.KArraySortBase import org.ksmt.utils.mkConst -open class UExprTranslator constructor( - ctx: UContext, -) : UExprTransformer(ctx) { - private val observers = mutableListOf() - - internal fun attachObserver(observer: UTranslationObserver) { - observers += observer +interface URegionTranslatorProvider : URegionIdVisitor> { + fun provide( + regionId: URegionId, + ): URegionTranslator, Key, Sort, *> { + @Suppress("UNCHECKED_CAST") + return apply(regionId) as URegionTranslator, Key, Sort, *> } +} + +typealias URegionIdInitialValueProvider = URegionIdVisitor> + +open class UExprTranslator constructor( + override val ctx: UContext, +) : UExprTransformer(ctx), URegionTranslatorProvider { open fun translate(expr: UExpr): UExpr = apply(expr) @@ -20,7 +29,6 @@ open class UExprTranslator constructor( override fun transform(expr: URegisterReading): UExpr { val registerConst = expr.sort.mkConst("r${expr.idx}") - observers.forEach { it.newRegisterReadingTranslated(expr.idx, registerConst) } return registerConst } @@ -34,13 +42,11 @@ open class UExprTranslator constructor( override fun transform(expr: UIndexedMethodReturnValue): UExpr { val const = expr.sort.mkConst("m${expr.method}_${expr.callIndex}") - observers.forEach { it.newIndexedMethodReturnValueTranslated(expr.method, expr.callIndex, const) } return const } override fun transform(expr: UNullRef): UExpr { val const = expr.sort.mkConst("null") - observers.forEach { it.nullRefTranslated(const) } return const } @@ -72,70 +78,86 @@ open class UExprTranslator constructor( translateRegionReading(expr.region, address) } - private val regionToTranslator = mutableMapOf, URegionTranslator<*, *, *, *>>() - .withDefault { regionId -> - val regionTranslator = regionId.translator(this) - observers.forEach { it.newRegionTranslator(regionId, regionTranslator) } - regionTranslator - } - open fun translateRegionReading( region: UMemoryRegion, Key, Sort>, key: Key, ): UExpr { - @Suppress("UNCHECKED_CAST") - val translator = - regionToTranslator.getValue(region.regionId) as URegionTranslator, Key, Sort, *> + val translator = provide(region.regionId) return translator.translateReading(region, key) } -} -internal typealias RegionTranslatorConstructor = (UExprTranslator<*, *>) -> URegionTranslator, T, U, *> + override fun visit( + regionId: UInputFieldId, + ): URegionTranslator, UHeapRef, Sort, *> { + val initialValue = regionIdInitialValueProvider.visit(regionId) + val updateTranslator = U1DArrayUpdateTranslator(this, initialValue) + val updatesTranslator = UFlatUpdatesTranslator(updateTranslator) + return URegionTranslator(updateTranslator, updatesTranslator) + } + + override fun visit( + regionId: UAllocatedArrayId, + ): URegionTranslator, USizeExpr, Sort, *> { + val initialValue = regionIdInitialValueProvider.visit(regionId) + val updateTranslator = U1DArrayUpdateTranslator(this, initialValue) + val updatesTranslator = UTreeUpdatesTranslator(updateTranslator) + return URegionTranslator(updateTranslator, updatesTranslator) + } -// TODO: maybe split this function into functions of URegionID -internal val URegionId.translator: RegionTranslatorConstructor - get() = { translator -> - val ctx = sort.uctx - @Suppress("UNCHECKED_CAST") - when (this) { - is UInputArrayId<*, Sort> -> { - val updateTranslator = U2DArrayUpdateTranslator(translator, ctx.addressSort, ctx.sizeSort, this) - val updatesTranslator = UTreeUpdatesTranslator(updateTranslator) - URegionTranslator(updateTranslator, updatesTranslator) - } - is UAllocatedArrayId<*, Sort> -> { - val updateTranslator = U1DArrayUpdateTranslator(translator, ctx.sizeSort, this) - val updatesTranslator = UTreeUpdatesTranslator(updateTranslator) - URegionTranslator(updateTranslator, updatesTranslator) - } - is UInputArrayLengthId<*> -> { - val updateTranslator = U1DArrayUpdateTranslator(translator, ctx.addressSort, this) - val updatesTranslator = UFlatUpdatesTranslator(updateTranslator) - URegionTranslator(updateTranslator, updatesTranslator) - } - is UInputFieldRegionId<*, Sort> -> { - val updateTranslator = U1DArrayUpdateTranslator(translator, ctx.addressSort, this) - val updatesTranslator = UFlatUpdatesTranslator(updateTranslator) - URegionTranslator(updateTranslator, updatesTranslator) - } - else -> error("Unexpected regionId: $this") - } as URegionTranslator, Key, Sort, *> - } - -// TODO: looks odd, because we duplicate StackEvaluator::eval, MockEvaluator::eval with slightly changed signature... -internal interface UTranslationObserver { - fun newRegionTranslator( - regionId: URegionId<*, *>, - translator: URegionTranslator<*, *, *, *>, - ) - - fun newRegisterReadingTranslated(idx: Int, translated: UExpr) - - fun newIndexedMethodReturnValueTranslated( - method: Method, - callIndex: Int, - translated: UExpr, - ) - - fun nullRefTranslated(translated: UExpr) + override fun visit( + regionId: UInputArrayId, + ): URegionTranslator, USymbolicArrayIndex, Sort, *> { + val initialValue = regionIdInitialValueProvider.visit(regionId) + val updateTranslator = U2DArrayUpdateTranslator(this, initialValue) + val updatesTranslator = UTreeUpdatesTranslator(updateTranslator) + return URegionTranslator(updateTranslator, updatesTranslator) + } + + override fun visit( + regionId: UInputArrayLengthId, + ): URegionTranslator, UHeapRef, USizeSort, *> { + val initialValue = regionIdInitialValueProvider.visit(regionId) + val updateTranslator = U1DArrayUpdateTranslator(this, initialValue) + val updatesTranslator = UFlatUpdatesTranslator(updateTranslator) + return URegionTranslator(updateTranslator, updatesTranslator) + } + + val regionIdInitialValueProvider = URegionIdInitialValueProviderBase(onDefaultValuePresent = { translate(it) }) } + +class URegionIdInitialValueProviderBase( + val onDefaultValuePresent: (UExpr<*>) -> UExpr<*>, +) : URegionIdVisitor>> { + override fun visit(regionId: UInputFieldId): UExpr> { + require(regionId.defaultValue == null) + return with(regionId.sort.uctx) { + mkArraySort(addressSort, regionId.sort).mkConst(regionId.toString()) + } + } + + override fun visit(regionId: UAllocatedArrayId): UExpr> { + @Suppress("SENSELESS_COMPARISON") + require(regionId.defaultValue != null) + return with(regionId.sort.uctx) { + val sort = mkArraySort(sizeSort, regionId.sort) + + @Suppress("UNCHECKED_CAST") + val value = onDefaultValuePresent(regionId.defaultValue) as UExpr + mkArrayConst(sort, value) + } + } + + override fun visit(regionId: UInputArrayId): UExpr> { + require(regionId.defaultValue == null) + return with(regionId.sort.uctx) { + mkArraySort(addressSort, sizeSort, regionId.sort).mkConst(regionId.toString()) + } + } + + override fun visit(regionId: UInputArrayLengthId): UExpr> { + require(regionId.defaultValue == null) + return with(regionId.sort.uctx) { + mkArraySort(addressSort, sizeSort).mkConst(regionId.toString()) + } + } +} \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/Expressions.kt b/usvm-core/src/main/kotlin/org/usvm/Expressions.kt index 1bdd84b269..cc08159d36 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Expressions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Expressions.kt @@ -138,7 +138,7 @@ class UInputFieldReading internal constructor( ctx: UContext, region: UInputFieldRegion, val address: UHeapRef, -) : UHeapReading, UHeapRef, Sort>(ctx, region) { +) : UHeapReading, UHeapRef, Sort>(ctx, region) { @Suppress("UNCHECKED_CAST") override fun accept(transformer: KTransformerBase): KExpr { require(transformer is UExprTransformer<*, *>) diff --git a/usvm-core/src/main/kotlin/org/usvm/Heap.kt b/usvm-core/src/main/kotlin/org/usvm/Heap.kt index b0daf73c99..74847d3d45 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Heap.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Heap.kt @@ -14,6 +14,8 @@ interface UReadOnlyHeap { * Returns a copy of the current map to be able to modify it without changing the original one. */ fun toMutableHeap(): UHeap + + fun nullRef(): Ref } typealias UReadOnlySymbolicHeap = UReadOnlyHeap, USizeExpr, Field, ArrayType, UBoolExpr> @@ -34,6 +36,8 @@ class UEmptyHeap(private val ctx: UContext) : UReadOnlySymboli override fun toMutableHeap(): UHeap, USizeExpr, Field, ArrayType, UBoolExpr> = URegionHeap(ctx) + + override fun nullRef(): UHeapRef = ctx.nullRef } interface UHeap : @@ -327,6 +331,8 @@ data class URegionHeap( allocatedLengths, inputLengths ) + override fun nullRef(): UHeapRef = ctx.nullRef + override fun toMutableHeap() = clone() } diff --git a/usvm-core/src/main/kotlin/org/usvm/HeapRefSplittingUtil.kt b/usvm-core/src/main/kotlin/org/usvm/HeapRefSplittingUtil.kt index 65b43aea70..c787bf22ba 100644 --- a/usvm-core/src/main/kotlin/org/usvm/HeapRefSplittingUtil.kt +++ b/usvm-core/src/main/kotlin/org/usvm/HeapRefSplittingUtil.kt @@ -173,12 +173,12 @@ internal inline fun filter( is UIteExpr -> when (state) { LEFT_CHILD -> { nodeToChild += guarded to RIGHT_CHILD - val leftGuard = mkAndNoFlat(guardFromTop, cur.condition) + val leftGuard = mkAnd(guardFromTop, cur.condition, flat = false) nodeToChild += (cur.trueBranch with leftGuard) to LEFT_CHILD } RIGHT_CHILD -> { nodeToChild += guarded to DONE - val guardRhs = mkAndNoFlat(guardFromTop, !cur.condition) + val guardRhs = mkAnd(guardFromTop, !cur.condition, flat = false) nodeToChild += (cur.falseBranch with guardRhs) to LEFT_CHILD } DONE -> { @@ -188,9 +188,9 @@ internal inline fun filter( val lhs = completelyMapped.removeLast() val next = when { lhs != null && rhs != null -> { - val leftPart = mkOrNoFlat(!cur.condition, lhs.guard) + val leftPart = mkOr(!cur.condition, lhs.guard, flat = false) - val rightPart = mkOrNoFlat(cur.condition, rhs.guard) + val rightPart = mkOr(cur.condition, rhs.guard, flat = false) /** *``` @@ -203,15 +203,15 @@ internal inline fun filter( * lhs.expr | lhs.guard rhs.expr | rhs.guard *``` */ - val guard = mkAndNoFlat(leftPart, rightPart) + val guard = mkAnd(leftPart, rightPart, flat = false) mkIte(cur.condition, lhs.expr, rhs.expr) with guard } lhs != null -> { - val guard = mkAndNoFlat(listOf(cur.condition, lhs.guard)) + val guard = mkAnd(cur.condition, lhs.guard, flat = false) lhs.expr with guard } rhs != null -> { - val guard = mkAndNoFlat(listOf(!cur.condition, rhs.guard)) + val guard = mkAnd(!cur.condition, rhs.guard, flat = false) rhs.expr with guard } else -> null diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt index d64bbaf390..212eaad824 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt @@ -1,5 +1,6 @@ package org.usvm +import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.utils.asExpr import org.usvm.util.SetRegion import org.usvm.util.emptyRegionTree @@ -28,7 +29,7 @@ data class UMemoryRegion, Key, Sort : USort> private val instantiator: UInstantiator, ) { val sort: Sort get() = regionId.sort - private val defaultValue = regionId.defaultValue + val defaultValue = regionId.defaultValue private fun read(key: Key, updates: UMemoryUpdates): UExpr { val lastUpdatedElement = updates.lastUpdatedElementOrNull() @@ -236,7 +237,7 @@ class GuardBuilder(nonMatchingUpdates: UBoolExpr) { * @return [expr] guarded by this guard builder. Implementation uses [UContext.mkAndNoFlat], because we accumulate * [nonMatchingUpdatesGuard] and otherwise it would take quadratic time. */ - fun guarded(expr: UBoolExpr): UBoolExpr = expr.ctx.mkAndNoFlat(nonMatchingUpdatesGuard, expr) + fun guarded(expr: UBoolExpr): UBoolExpr = expr.ctx.mkAnd(nonMatchingUpdatesGuard, expr, flat = false) } //endregion @@ -303,7 +304,7 @@ fun refIndexRangeRegion( idx2: USymbolicArrayIndex, ): UArrayIndexRegion = indexRangeRegion(idx1.second, idx2.second) -typealias UInputFieldRegion = UMemoryRegion, UHeapRef, Sort> +typealias UInputFieldRegion = UMemoryRegion, UHeapRef, Sort> typealias UAllocatedArrayRegion = UMemoryRegion, USizeExpr, Sort> typealias UInputArrayRegion = UMemoryRegion, USymbolicArrayIndex, Sort> typealias UInputArrayLengthRegion = UMemoryRegion, UHeapRef, USizeSort> @@ -327,9 +328,9 @@ val UInputArrayLengthRegion.inputLengthArrayType fun emptyInputFieldRegion( field: Field, sort: Sort, - instantiator: UInstantiator, UHeapRef, Sort>, + instantiator: UInstantiator, UHeapRef, Sort>, ): UInputFieldRegion = UMemoryRegion( - UInputFieldRegionId(field, sort), + UInputFieldId(field, sort), UFlatUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic), instantiator ) @@ -344,7 +345,7 @@ fun emptyAllocatedArrayRegion( updates = emptyRegionTree(), ::indexRegion, ::indexRangeRegion, ::indexEq, ::indexLeConcrete, ::indexLeSymbolic ) - val regionId = UAllocatedArrayId(arrayType, address, sort) + val regionId = UAllocatedArrayId(arrayType, address, sort, sort.sampleValue()) return UMemoryRegion(regionId, updates, instantiator) } diff --git a/usvm-core/src/main/kotlin/org/usvm/Mocks.kt b/usvm-core/src/main/kotlin/org/usvm/Mocks.kt index b17eabc9dd..bc9a652cd4 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Mocks.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Mocks.kt @@ -37,8 +37,6 @@ interface UMocker : UMockEvaluator { args: Sequence>, sort: Sort ): Pair, UMocker> - - fun decode(model: KModel): UMockEvaluator } class UIndexedMocker( @@ -57,9 +55,5 @@ class UIndexedMocker( return Pair(const, UIndexedMocker(ctx, updatedClauses)) } - override fun decode(model: KModel): UMockEvaluator { - TODO("Not yet implemented") - } - override fun eval(symbol: UMockSymbol): UExpr = symbol } diff --git a/usvm-core/src/main/kotlin/org/usvm/Model.kt b/usvm-core/src/main/kotlin/org/usvm/Model.kt index 444e6f5e94..115e2f5f24 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Model.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Model.kt @@ -1,5 +1,7 @@ package org.usvm +import org.ksmt.expr.rewrite.KExprSubstitutor + interface UModel { fun eval(expr: UExpr): UExpr } @@ -12,11 +14,9 @@ open class UModelBase( val heap: UReadOnlySymbolicHeap, val types: UTypeModel, val mocks: UMockEvaluator -) - : UModel -{ - override fun eval(expr: UExpr): UExpr { - val composer = UComposer(ctx, stack, heap, types, mocks) - return composer.compose(expr) - } +) : UModel { + private val composer = UComposer(ctx, stack, heap, types, mocks) + + override fun eval(expr: UExpr): UExpr = + composer.compose(expr) } \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt new file mode 100644 index 0000000000..ef2550c8cf --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt @@ -0,0 +1,196 @@ +package org.usvm + +import org.ksmt.expr.KArray2Store +import org.ksmt.expr.KArrayConst +import org.ksmt.expr.KArrayStore +import org.ksmt.expr.KExpr +import org.ksmt.solver.KModel +import org.ksmt.sort.KArray2Sort +import org.ksmt.sort.KArraySort +import org.ksmt.utils.cast +import org.usvm.UModelDecoderBase.Companion.mapAddress + +interface URegionEvaluator { + fun select(key: Key): UExpr + fun write(key: Key, expr: UExpr) + fun clone(): URegionEvaluator +} + +/** + * A specific evaluator for one-dimensional regions generalized by a single expression of a [KeySort]. + */ +class U1DArrayEvaluator private constructor( + private val values: MutableMap, UExpr>, + private val constValue: UExpr, +) : URegionEvaluator, Sort> { + + /** + * A constructor that is used in cases when we try to evaluate + * an expression from a region that was never translated. + */ + constructor( + mappedConstValue: UExpr, + ) : this(mutableMapOf(), mappedConstValue) + + override fun select(key: KExpr): UExpr = values.getOrDefault(key, constValue) + + override fun write(key: KExpr, expr: UExpr) { + values[key] = expr + } + + override fun clone(): U1DArrayEvaluator = + U1DArrayEvaluator( + values.toMutableMap(), + constValue + ) + + companion object { + /** + * A constructor that is used in regular cases for a region + * that has a corresponding translator. It collects information + * required for the region decoding using data about translated expressions, + * resolved values from the [model] and the [mapping] from address expressions + * to their concrete representation. + */ + operator fun invoke( + initialValue: KExpr>, + model: KModel, + mapping: Map, + ): U1DArrayEvaluator { + // Since the model contains only information about values we got from the outside, + // we can translate and ask only about an initial value for the region. + // All other values should be resolved earlier without asking the model. + val evaluatedArray = model.eval(initialValue, isComplete = true) + + var valueCopy = evaluatedArray + + val stores = mutableMapOf, UExpr>() + + // Parse stores into the region, then collect a const value for the evaluated region. + while (valueCopy !is KArrayConst<*, *>) { + require(valueCopy is KArrayStore) + + val value = valueCopy.value.mapAddress(mapping) + + val mapAddress = valueCopy.index.mapAddress(mapping) + stores[mapAddress] = value + valueCopy = valueCopy.array + } + @Suppress("UNCHECKED_CAST") + valueCopy as KArrayConst, Sort> + + val constValue = valueCopy.value.mapAddress(mapping) + val values = stores + return U1DArrayEvaluator(values, constValue) + } + } +} + +/** + * A specific evaluator for two-dimensional regions generalized be a pair + * of two expressions with [Key1Sort] and [Key2Sort] sorts. + */ +class U2DArrayEvaluator private constructor( + val values: MutableMap, UExpr>, UExpr>, + val constValue: UExpr, +) : URegionEvaluator, KExpr>, Sort> { + /** + * A constructor that is used in cases when we try to evaluate + * an expression from a region that was never translated. + */ + constructor( + mappedConstValue: UExpr, + ) : this(mutableMapOf(), mappedConstValue) + + override fun select(key: Pair, KExpr>): UExpr { + return values.getOrDefault(key, constValue) + } + + override fun write(key: Pair, KExpr>, expr: UExpr) { + values[key] = expr + } + + override fun clone(): U2DArrayEvaluator = + U2DArrayEvaluator( + values.toMutableMap(), + constValue + ) + + companion object { + /** + * A constructor that is used in regular cases for a region + * that has a corresponding translator. It collects information + * required for the region decoding using data about translated expressions, + * resolved values from the [model] and the [mapping] from address expressions + * to their concrete representation. + */ + operator fun invoke( + initialValue: KExpr>, + model: KModel, + mapping: Map, + ): U2DArrayEvaluator { + val evaluatedArray = model.eval(initialValue, isComplete = true) + + var valueCopy = evaluatedArray + + val stores = mutableMapOf, UExpr>, UExpr>() + + while (valueCopy !is KArrayConst<*, *>) { + require(valueCopy is KArray2Store) + + val value = valueCopy.value.mapAddress(mapping) + + val index0 = valueCopy.index0.mapAddress(mapping) + val index1 = valueCopy.index1.mapAddress(mapping) + + stores[index0 to index1] = value + valueCopy = valueCopy.array + } + + @Suppress("UNCHECKED_CAST") + valueCopy as KArrayConst, Sort> + + val constValue = valueCopy.value.mapAddress(mapping) + val values = stores + return U2DArrayEvaluator(values, constValue) + } + } +} + +interface URegionEvaluatorProvider { + fun provide( + regionId: URegionId, + ): URegionEvaluator +} + +open class URegionEvaluatorProviderFromKModel( + private val model: KModel, + private val mapping: Map, UConcreteHeapRef>, + private val regionIdInitialValueProvider: URegionIdInitialValueProvider, +) : URegionEvaluatorProvider, URegionIdVisitor> { + + /** + * Returns an evaluator for [regionId]. + * Note that it is a translator-specific evaluator that + * knows how to decode values from the [model]. + * + * [mapping] contains information about matching expressions of the + * address sort and their concrete (evaluated) representations. + */ + @Suppress("UNCHECKED_CAST") + override fun provide( + regionId: URegionId, + ): URegionEvaluator = apply(regionId) as URegionEvaluator + + override fun visit(regionId: UInputFieldId): URegionEvaluator = + U1DArrayEvaluator(regionIdInitialValueProvider.apply(regionId).cast(), model, mapping) + + override fun visit(regionId: UAllocatedArrayId): URegionEvaluator = + U1DArrayEvaluator(regionIdInitialValueProvider.apply(regionId).cast(), model, mapping) + + override fun visit(regionId: UInputArrayId): URegionEvaluator = + U2DArrayEvaluator(regionIdInitialValueProvider.apply(regionId).cast(), model, mapping) + + override fun visit(regionId: UInputArrayLengthId): URegionEvaluator = + U1DArrayEvaluator(regionIdInitialValueProvider.apply(regionId).cast(), model, mapping) +} \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt index 95cbac1c79..4f21c5e741 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt @@ -3,7 +3,6 @@ package org.usvm import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.utils.asExpr - /** * An interface that represents any possible type of regions that can be used in the memory. */ @@ -26,16 +25,33 @@ interface URegionId { fun keyMapper(transformer: UExprTransformer): KeyMapper fun map(composer: UComposer): URegionId + + fun accept(visitor: URegionIdVisitor): R +} + +interface URegionIdVisitor { + fun apply(regionId: URegionId): R = regionId.accept(this) + + fun visit(regionId: URegionId): Any? = + error("You must provide visit implementation for ${regionId::class} in ${this::class}") + + fun visit(regionId: UInputFieldId): R + + fun visit(regionId: UAllocatedArrayId): R + + fun visit(regionId: UInputArrayId): R + + fun visit(regionId: UInputArrayLengthId): R } /** * A region id for a region storing the specific [field]. */ -data class UInputFieldRegionId internal constructor( +data class UInputFieldId internal constructor( val field: Field, override val sort: Sort, ) : URegionId { - override val defaultValue get() = null + override val defaultValue: UExpr? get() = null @Suppress("UNCHECKED_CAST") override fun read( @@ -55,9 +71,12 @@ data class UInputFieldRegionId internal constructor( transformer: UExprTransformer, ): KeyMapper = { transformer.apply(it) } - override fun map(composer: UComposer): UInputFieldRegionId = + override fun map(composer: UComposer): UInputFieldId = this + override fun accept(visitor: URegionIdVisitor): R = + visitor.visit(this) + override fun toString(): String { return "inputField($field)" } @@ -75,8 +94,8 @@ data class UAllocatedArrayId internal constructor( override val arrayType: ArrayType, val address: UConcreteHeapAddress, override val sort: Sort, + override val defaultValue: UExpr, ) : UArrayId { - override val defaultValue get() = sort.sampleValue() @Suppress("UNCHECKED_CAST") override fun read( @@ -103,7 +122,7 @@ data class UAllocatedArrayId internal constructor( ): KeyMapper = { transformer.apply(it) } override fun map(composer: UComposer): UAllocatedArrayId = - this + copy(defaultValue = composer.compose(defaultValue)) // we don't include arrayType into hashcode and equals, because [address] already defines unambiguously override fun equals(other: Any?): Boolean { @@ -117,6 +136,9 @@ data class UAllocatedArrayId internal constructor( return true } + override fun accept(visitor: URegionIdVisitor): R = + visitor.visit(this) + override fun hashCode(): Int { return address } @@ -133,7 +155,7 @@ data class UInputArrayId internal constructor( override val arrayType: ArrayType, override val sort: Sort, ) : UArrayId { - override val defaultValue get() = null + override val defaultValue: UExpr? get() = null @Suppress("UNCHECKED_CAST") override fun read( @@ -157,6 +179,9 @@ data class UInputArrayId internal constructor( if (ref === it.first && idx === it.second) it else ref to idx } + override fun accept(visitor: URegionIdVisitor): R = + visitor.visit(this) + override fun map(composer: UComposer): UInputArrayId = this @@ -172,7 +197,7 @@ data class UInputArrayLengthId internal constructor( val arrayType: ArrayType, override val sort: USizeSort, ) : URegionId { - override val defaultValue get() = null + override val defaultValue: UExpr? get() = null @Suppress("UNCHECKED_CAST") override fun read( @@ -198,6 +223,9 @@ data class UInputArrayLengthId internal constructor( override fun map(composer: UComposer): UInputArrayLengthId = this + override fun accept(visitor: URegionIdVisitor): R = + visitor.visit(this) + override fun toString(): String { return "length($arrayType)" } diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt index c83cd8ec7e..44f73a3040 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt @@ -1,14 +1,7 @@ package org.usvm -import org.ksmt.expr.KArray2Store -import org.ksmt.expr.KArrayConst -import org.ksmt.expr.KArrayStore -import org.ksmt.solver.KModel -import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.sort.KArray2Sort import org.ksmt.sort.KArraySort -import org.ksmt.utils.mkConst -import org.usvm.UModelDecoderBase.Companion.mapAddress import org.usvm.util.Region import org.usvm.util.RegionTree import java.util.IdentityHashMap @@ -19,65 +12,37 @@ import java.util.IdentityHashMap class URegionTranslator, Key, Sort : USort, Result>( private val updateTranslator: UUpdateTranslator, private val updatesTranslator: UUpdatesTranslator, -) : UUpdateTranslator by updateTranslator { +) { fun translateReading(region: UMemoryRegion, key: Key): UExpr { val translated = translate(region) return updateTranslator.select(translated, key) } + fun initialValue(): Result = updateTranslator.initialValue() + private val cache = IdentityHashMap, Result>() private fun translate(region: UMemoryRegion): Result = cache.getOrPut(region) { updatesTranslator.translateUpdates(region.updates) } } -interface URegionEvaluator { - fun select(key: Key): UExpr - fun write(key: Key, expr: UExpr) -} - interface UUpdateTranslator { fun select(result: Result, key: Key): UExpr - /** - * Returns an evaluator for the current translator. - * Note that it is a translator-specific evaluator that - * knows how to decode values from the [model]. - * - * [mapping] contains information about matching expressions of the - * address sort and their concrete (evaluated) representations. - */ - fun getEvaluator(model: KModel, mapping: Map): URegionEvaluator - fun initialValue(): Result fun applyUpdate(previous: Result, update: UUpdateNode): Result } -internal class U1DArrayUpdateTranslator, Sort>, KeySort : USort, Sort : USort>( - private val translator: UExprTranslator<*, *>, - private val keySort: KeySort, - private val regionId: RegionId, +internal class U1DArrayUpdateTranslator( + private val exprTranslator: UExprTranslator<*, *>, + private val initialValue: UExpr> + ) : UUpdateTranslator, Sort, UExpr>> { override fun select(result: UExpr>, key: UExpr): UExpr = result.ctx.mkArraySelect(result, key) - override fun getEvaluator( - model: KModel, - mapping: Map, - ): URegionEvaluator, Sort> = U1DArrayEvaluator(translator = this, model, mapping) - - override fun initialValue(): UExpr> { - val ctx = regionId.sort.uctx - val arraySort = ctx.mkArraySort(keySort, regionId.sort) - val defaultValue = regionId.defaultValue - val initialArray = if (defaultValue == null) { - arraySort.mkConst(regionId.toString()) - } else { - ctx.mkArrayConst(arraySort, defaultValue) - } - return initialArray - } + override fun initialValue(): UExpr> = initialValue override fun applyUpdate( previous: UExpr>, @@ -102,9 +67,9 @@ internal class U1DArrayUpdateTranslator, Sor val key = mkFreshConst("k", previous.sort.domain) val from = update.region - val convertedKey = from.regionId.keyMapper(translator)(update.keyConverter.convert(key)) + val convertedKey = from.regionId.keyMapper(exprTranslator)(update.keyConverter.convert(key)) val isInside = update.includesSymbolically(key).translated // already includes guard - val result = translator.translateRegionReading(from, convertedKey) + val result = exprTranslator.translateRegionReading(from, convertedKey) val ite = mkIte(isInside, result, previous.select(key)) mkArrayLambda(key.decl, ite) } @@ -113,116 +78,26 @@ internal class U1DArrayUpdateTranslator, Sor } } - private val UExpr.translated get() = translator.translate(this) - - /** - * A specific evaluator for one-dimensional regions generalized by a single expression of a [KeySort]. - */ - class U1DArrayEvaluator, Sort>, KeySort : USort, Sort : USort> private constructor() : - URegionEvaluator, Sort> { - - private lateinit var values: MutableMap, UExpr> - private lateinit var constValue: UExpr - - /** - * A constructor that is used in regular cases for a region - * that has a corresponding translator. It collects information - * required for the region decoding using data about translated expressions, - * resolved values from the [model] and the [mapping] from address expressions - * to their concrete representation. - */ - constructor( - translator: U1DArrayUpdateTranslator, - model: KModel, - mapping: Map, - ) : this() { - // Since the model contains only information about values we got from the outside, - // we can translate and ask only about an initial value for the region. - // All other values should be resolved earlier without asking the model. - val initialValue = translator.initialValue() - val evaluatedArray = model.eval(initialValue, isComplete = true) - - var valueCopy = evaluatedArray - - val stores = mutableMapOf, UExpr>() - - // Parse stores into the region, then collect a const value for the evaluated region. - while (valueCopy !is KArrayConst<*, *>) { - require(valueCopy is KArrayStore) - - val value = valueCopy.value.mapAddress(mapping) - - val mapAddress = valueCopy.index.mapAddress(mapping) - stores[mapAddress] = value - valueCopy = valueCopy.array - } - @Suppress("UNCHECKED_CAST") - valueCopy as KArrayConst, Sort> - - constValue = valueCopy.value.mapAddress(mapping) - values = stores - } - - /** - * A constructor that is used in cases when we try to evaluate - * an expression from a region that was never translated. - */ - constructor( - regionId: RegionId, - mapping: Map, - ) : this() { - // If some region has a default value, it means that the region is an allocated one. - // All such regions must be processed earlier, and we won't have them here. - require(regionId.defaultValue == null) - - // So, for these region we should take sample values for theis sorts. - val unmappedConstValue = regionId.sort.sampleValue() - - values = mutableMapOf() - // Because of this, we can get rid of evaluation and return a possibly mapped interpreted value. - constValue = unmappedConstValue.mapAddress(mapping) - } - - override fun select(key: UExpr): UExpr = values.getOrDefault(key, constValue) - - override fun write(key: UExpr, expr: UExpr) { - values[key] = expr - } - } + private val UExpr.translated get() = exprTranslator.translate(this) } -internal class U2DArrayUpdateTranslator, UExpr>, Sort>, Key1Sort : USort, Key2Sort : USort, Sort : USort>( - private val translator: UExprTranslator<*, *>, - private val key1Sort: Key1Sort, - private val key2Sort: Key2Sort, - private val regionId: RegionId, +internal class U2DArrayUpdateTranslator< + Key1Sort : USort, + Key2Sort : USort, + Sort : USort>( + private val exprTranslator: UExprTranslator<*, *>, + private val initialValue: UExpr>, ) : UUpdateTranslator, UExpr>, Sort, UExpr>> { + /** + * [key] is already translated, so we don't have to call it explicitly. + */ override fun select( result: UExpr>, key: Pair, UExpr>, ): UExpr = - result.ctx.mkArraySelect(result, key.first.translated, key.second.translated) - - override fun getEvaluator( - model: KModel, - mapping: Map, - ): URegionEvaluator, UExpr>, Sort> { - return U2DArrayEvaluator(translator = this, model, mapping) - } + result.ctx.mkArraySelect(result, key.first, key.second) - override fun initialValue(): UExpr> { - val ctx = regionId.sort.uctx - val arraySort = ctx.mkArraySort(key1Sort, key2Sort, regionId.sort) - - val defaultValue = regionId.defaultValue - val initialArray = if (defaultValue == null) { - arraySort.mkConst(regionId.toString()) - } else { - ctx.mkArrayConst(arraySort, defaultValue.translated) - } - - return initialArray - } + override fun initialValue(): UExpr> = initialValue override fun applyUpdate( previous: UExpr>, @@ -248,9 +123,9 @@ internal class U2DArrayUpdateTranslator UExpr.translated get() = translator.translate(this) - - /** - * A specific evaluator for two-dimensional regions generalized be a pair - * of two expressions with [Key1Sort] and [Key2Sort] sorts. - */ - class U2DArrayEvaluator, UExpr>, Sort>, Key1Sort : USort, Key2Sort : USort, Sort : USort> private constructor( - ) : URegionEvaluator, UExpr>, Sort> { - private lateinit var values: MutableMap, UExpr>, UExpr> - private lateinit var constValue: UExpr - - /** - * A constructor that is used in regular cases for a region - * that has a corresponding translator. It collects information - * required for the region decoding using data about translated expressions, - * resolved values from the [model] and the [mapping] from address expressions - * to their concrete representation. - */ - constructor( - translator: U2DArrayUpdateTranslator, - model: KModel, - mapping: Map, - ) : this() { - val initialValue = translator.initialValue() - val evaluatedArray = model.eval(initialValue, isComplete = true) - - var valueCopy = evaluatedArray - - val stores = mutableMapOf, UExpr>, UExpr>() - - while (valueCopy !is KArrayConst<*, *>) { - require(valueCopy is KArray2Store) - - val value = valueCopy.value.mapAddress(mapping) - - val index0 = valueCopy.index0.mapAddress(mapping) - val index1 = valueCopy.index1.mapAddress(mapping) - - stores[index0 to index1] = value - valueCopy = valueCopy.array - } - - @Suppress("UNCHECKED_CAST") - valueCopy as KArrayConst, Sort> - - constValue = valueCopy.value.mapAddress(mapping) - values = stores - } - - /** - * A constructor that is used in cases when we try to evaluate - * an expression from a region that was never translated. - */ - constructor( - regionId: RegionId, - mapping: Map, - ) : this() { - // If some region has a default value, it means that the region is an allocated one. - // All such regions must be processed earlier, and we won't have them here. - require(regionId.defaultValue == null) - - // So, for these region we should take sample values for theis sorts. - val unmappedConstValue = regionId.sort.sampleValue() - - values = mutableMapOf() - // Because of this, we can get rid of evaluation and return a possibly mapped interpreted value. - constValue = unmappedConstValue.mapAddress(mapping) - } - - override fun select(key: Pair, UExpr>): UExpr { - return values.getOrDefault(key, constValue) - } - - override fun write(key: Pair, UExpr>, expr: UExpr) { - values[key] = expr - } - } + private val UExpr.translated get() = exprTranslator.translate(this) } interface UUpdatesTranslator { diff --git a/usvm-core/src/main/kotlin/org/usvm/Solver.kt b/usvm-core/src/main/kotlin/org/usvm/Solver.kt new file mode 100644 index 0000000000..4dc7c0a29e --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/Solver.kt @@ -0,0 +1,61 @@ +package org.usvm.concrete.interpreter + +import org.ksmt.solver.KSolver +import org.ksmt.solver.KSolverStatus +import org.usvm.UContext +import org.usvm.UExprTranslator +import org.usvm.UMemoryBase +import org.usvm.UModelBase +import org.usvm.UModelDecoder +import org.usvm.UModelDecoderBase +import org.usvm.UPathCondition + +sealed interface USolverResult + +open class USolverSat( + val model: Model +) : USolverResult + +open class USolverUnsat : USolverResult + +open class USolverUnknown : USolverResult + +abstract class USolver { + abstract fun check(memory: Memory, pc: PathCondition): USolverResult +} + +open class USolverBase( + val ctx: UContext, + val solver: KSolver<*>, + val translator: UExprTranslator, + val evaluator: UModelDecoder, UModelBase>, +) : USolver, UPathCondition, UModelBase>() { + + override fun check(memory: UMemoryBase, pc: UPathCondition): USolverResult> { + if (pc.isFalse) { + return USolverUnsat() + } + solver.push() + + for (constraint in pc) { + val translated = translator.apply(constraint) + solver.assert(translated) + } + + val status = solver.check() + if (status != KSolverStatus.SAT) { + solver.pop() + + return if (status == KSolverStatus.UNSAT) { + USolverUnsat() + } else { + USolverUnknown() + } + } + val model = solver.model().detach() + solver.pop() + + val uModel = evaluator.decode(memory, model) + return USolverSat(uModel) + } +} \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/TranslatorBuilder.kt b/usvm-core/src/main/kotlin/org/usvm/TranslatorBuilder.kt new file mode 100644 index 0000000000..84fd54b537 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/TranslatorBuilder.kt @@ -0,0 +1,109 @@ +package org.usvm + +import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue +import org.ksmt.utils.cast +import org.usvm.UModelDecoderBase.Companion.mapAddress + +private class UCachingExprTranslator( + ctx: UContext, +) : UExprTranslator(ctx) { + + val registerIdxToTranslated = mutableMapOf>() + + override fun transform(expr: URegisterReading): UExpr = + registerIdxToTranslated.getOrPut(expr.idx) { + super.transform(expr) + }.cast() + + val indexedMethodReturnValueToTranslated = mutableMapOf, UExpr<*>>() + + override fun transform(expr: UIndexedMethodReturnValue): UExpr = + indexedMethodReturnValueToTranslated.getOrPut(expr.method to expr.callIndex) { + super.transform(expr) + }.cast() + + val nullRef = super.translate(ctx.nullRef) + + override fun transform(expr: UNullRef): UExpr = nullRef + + val regionIdToTranslator = + mutableMapOf, URegionTranslator, *, *, *>>() + + override fun provide(regionId: URegionId): URegionTranslator, Key, Sort, *> = + regionIdToTranslator.getOrPut(regionId) { + super.provide(regionId).cast() + }.cast() +} + +private class UCachingRegionEvaluatorProvider( + val mapping: AddressesMapping, + translatedRegionIds: Set>, + regionEvaluatorProvider: URegionEvaluatorProviderFromKModel, +) : URegionEvaluatorProvider, URegionIdVisitor> { + + private val evaluatorsForTranslatedRegions: MutableMap, URegionEvaluator<*, *>> + + init { + evaluatorsForTranslatedRegions = translatedRegionIds.associateWithTo(mutableMapOf()) { regionId -> + regionEvaluatorProvider.apply(regionId) + } + } + + override fun provide(regionId: URegionId): URegionEvaluator = + evaluatorsForTranslatedRegions.getOrPut(regionId) { + apply(regionId) + }.cast() + + override fun visit(regionId: UInputFieldId): U1DArrayEvaluator { + // If some region has a default value, it means that the region is an allocated one. + // All such regions must be processed earlier, and we won't have them here. + require(regionId.defaultValue == null) + // So, for these region we should take sample values for theis sorts. + val mappedConstValue = regionId.sort.sampleValue().mapAddress(mapping) + return U1DArrayEvaluator(mappedConstValue) + } + + override fun visit(regionId: UAllocatedArrayId): URegionEvaluator<*, *> { + error("Allocated arrays should be evaluated implicitly") + } + + override fun visit(regionId: UInputArrayId): U2DArrayEvaluator { + // If some region has a default value, it means that the region is an allocated one. + // All such regions must be processed earlier, and we won't have them here. + require(regionId.defaultValue == null) + // So, for these region we should take sample values for theis sorts. + val mappedConstValue = regionId.sort.sampleValue().mapAddress(mapping) + return U2DArrayEvaluator(mappedConstValue) + } + + override fun visit(regionId: UInputArrayLengthId): U1DArrayEvaluator { + // If some region has a default value, it means that the region is an allocated one. + // All such regions must be processed earlier, and we won't have them here. + require(regionId.defaultValue == null) + // So, for these region we should take sample values for theis sorts. + val mappedConstValue = regionId.sort.sampleValue().mapAddress(mapping) + return U1DArrayEvaluator(mappedConstValue) + } +} + +fun buildDefaultTranslatorAndDecoder( + ctx: UContext, +): Pair, UModelDecoderBase> { + val translator = UCachingExprTranslator(ctx) + + val decoder = UModelDecoderBase( + translator.registerIdxToTranslated, + translator.indexedMethodReturnValueToTranslated, + translator.nullRef + ) { model, mapping -> + val regionEvaluatorProviderFromKModel = + URegionEvaluatorProviderFromKModel(model, mapping, translator.regionIdInitialValueProvider) + UCachingRegionEvaluatorProvider( + mapping, + translator.regionIdToTranslator.keys, + regionEvaluatorProviderFromKModel + ) + } + + return translator to decoder +} \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt index 11bf4c1449..188a1ba15b 100644 --- a/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt @@ -10,52 +10,28 @@ import org.ksmt.sort.KUninterpretedSort import org.ksmt.utils.asExpr import org.usvm.UAddressCounter.Companion.INITIAL_INPUT_ADDRESS import org.usvm.UAddressCounter.Companion.NULL_ADDRESS +import kotlinx.collections.immutable.mutate interface UModelDecoder { fun decode(memory: Memory, model: KModel): Model } -class UModelDecoderBase( - translator: UExprTranslator, -) : UModelDecoder, UModelBase>, UTranslationObserver { +typealias AddressesMapping = Map, UConcreteHeapRef> - private val ctx: UContext = translator.ctx as UContext - - init { - translator.attachObserver(this) - } - - private val regionToTranslator = mutableMapOf, URegionTranslator<*, *, *, *>>() - private val registerIdxToTranslated = mutableMapOf>() - private val indexedMethodReturnValueToTranslated = mutableMapOf, UExpr<*>>() - private lateinit var nullRef: UExpr - - override fun newRegionTranslator(regionId: URegionId<*, *>, translator: URegionTranslator<*, *, *, *>) { - regionToTranslator[regionId] = translator - } - - override fun newRegisterReadingTranslated(idx: Int, translated: UExpr) { - registerIdxToTranslated[idx] = translated - } - - override fun newIndexedMethodReturnValueTranslated( - method: Method, - callIndex: Int, - translated: UExpr, - ) { - indexedMethodReturnValueToTranslated[method to callIndex] = translated - } - - override fun nullRefTranslated(translated: UExpr) { - nullRef = translated - } +open class UModelDecoderBase( + protected val registerIdxToTranslated: Map>, + protected val indexedMethodReturnValueToTranslated: Map, UExpr<*>>, + protected val nullRef: UExpr, + protected val regionEvaluatorProviderBuilder: (KModel, AddressesMapping) -> URegionEvaluatorProvider, +) : UModelDecoder, UModelBase> { + private val ctx: UContext = nullRef.uctx /** * Build a mapping from instances of an uninterpreted [UAddressSort] * to [UConcreteHeapRef] with integer addresses. It allows us to enumerate * equivalence classes of addresses and work with their number in the future. */ - private fun buildMapping(model: KModel): Map, UConcreteHeapRef> { + private fun buildMapping(model: KModel): AddressesMapping { // Null is a special value that we want to translate in any case. val interpretedNullRef = model.eval(nullRef, isComplete = true) @@ -83,42 +59,10 @@ class UModelDecoderBase( @Suppress("UNCHECKED_CAST", "SafeCastWithReturn") private fun decodeHeap( model: KModel, - addressesMapping: Map, UConcreteHeapRef>, + addressesMapping: AddressesMapping, ): UHeapModel { - var inputFields = persistentMapOf>() - var inputArrays = persistentMapOf, out USort>>() - var inputLengths = persistentMapOf>() - - regionToTranslator.forEach { (regionId, translator) -> - when (regionId) { - is UInputFieldRegionId<*, *> -> { - regionId as? UInputFieldRegionId ?: return@forEach - - val evaluator = translator.getEvaluator(model, addressesMapping) as URegionEvaluator - inputFields = inputFields.put(regionId.field, evaluator) - } - - is UInputArrayId<*, *> -> { - regionId as? UInputArrayId ?: return@forEach - - val evaluator = translator.getEvaluator( - model, - addressesMapping - ) as URegionEvaluator, out USort> - inputArrays = inputArrays.put(regionId.arrayType, evaluator) - } - - is UInputArrayLengthId<*> -> { - regionId as? UInputArrayLengthId ?: return@forEach - - val evaluator = - translator.getEvaluator(model, addressesMapping) as URegionEvaluator - inputLengths = inputLengths.put(regionId.arrayType, evaluator) - } - } - } - - return UHeapModel(ctx, inputFields, inputArrays, inputLengths, addressesMapping) + val regionEvaluator = regionEvaluatorProviderBuilder(model, addressesMapping) + return UHeapModel(ctx, addressesMapping.getValue(ctx.nullRef), regionEvaluator, persistentMapOf(), persistentMapOf(), persistentMapOf()) } override fun decode( @@ -157,7 +101,7 @@ class UModelDecoderBase( */ private fun Map>.replaceUninterpretedConsts( model: KModel, - addressesMapping: Map, UConcreteHeapRef>, + addressesMapping: AddressesMapping, ): Map> { val values = mapValues { (_, expr) -> val value = model.eval(expr, isComplete = true) @@ -194,10 +138,11 @@ class URegistersStackModel(private val registers: Map>) : class UHeapModel( private val ctx: UContext, + private val nullRef: UExpr, + private val regionEvaluatorProvider: URegionEvaluatorProvider, private var resolvedInputFields: PersistentMap>, private var resolvedInputArrays: PersistentMap, out USort>>, private var resolvedInputLengths: PersistentMap>, - private val uninterpretedAddressesMapping: Map, ) : USymbolicHeap { override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr { // All the expressions in the model are interpreted, therefore, they must @@ -205,7 +150,15 @@ class UHeapModel( // which have addresses less or equal than INITIAL_INPUT_ADDRESS require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - return resolvedInputFields[field]?.select(ref)?.asExpr(sort) ?: sort.sampleValue() + @Suppress("UNCHECKED_CAST") + val regionEvaluator = resolvedInputFields.getOrElse(field) { + val regionId = UInputFieldId(field, ctx.sizeSort) + val evaluator = regionEvaluatorProvider.provide(regionId) + resolvedInputFields = resolvedInputFields.put(field, evaluator) + evaluator + } as URegionEvaluator + + return regionEvaluator.select(ref).asExpr(sort) } override fun readArrayIndex( @@ -217,19 +170,35 @@ class UHeapModel( // All the expressions in the model are interpreted, therefore, they must // have concrete addresses. Moreover, the model known only about input values // which have addresses less or equal than INITIAL_INPUT_ADDRESS - require(ref is UConcreteHeapRef && ref.address < INITIAL_INPUT_ADDRESS) + require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) val key = ref to index - return resolvedInputArrays[arrayType]?.select(key)?.asExpr(elementSort) ?: elementSort.sampleValue() + + @Suppress("UNCHECKED_CAST") + val regionEvaluator = resolvedInputArrays.getOrElse(arrayType) { + val regionId = UInputArrayId(arrayType, elementSort) + val evaluator = regionEvaluatorProvider.provide(regionId) + resolvedInputArrays = resolvedInputArrays.put(arrayType, evaluator) + evaluator + } as URegionEvaluator, Sort> + + return regionEvaluator.select(key).asExpr(elementSort) } override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType): USizeExpr { // All the expressions in the model are interpreted, therefore, they must // have concrete addresses. Moreover, the model known only about input values // which have addresses less or equal than INITIAL_INPUT_ADDRESS - require(ref is UConcreteHeapRef && ref.address < INITIAL_INPUT_ADDRESS) + require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - return resolvedInputLengths[arrayType]?.select(ref) ?: ctx.sizeSort.sampleValue() + val regionEvaluator = resolvedInputLengths.getOrElse(arrayType) { + val regionId = UInputArrayLengthId(arrayType, ctx.sizeSort) + val evaluator = regionEvaluatorProvider.provide(regionId) + resolvedInputLengths = resolvedInputLengths.put(arrayType, evaluator) + evaluator + } + + return regionEvaluator.select(ref) } override fun writeField( @@ -250,15 +219,14 @@ class UHeapModel( // All the expressions in the model are interpreted, therefore, they must // have concrete addresses. Moreover, the model known only about input values // which have addresses less or equal than INITIAL_INPUT_ADDRESS - require(ref is UConcreteHeapRef && ref.address < INITIAL_INPUT_ADDRESS) + require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) @Suppress("UNCHECKED_CAST") val regionEvaluator = resolvedInputFields.getOrElse(field) { - val regionId = UInputFieldRegionId(field, sort) - val result = U1DArrayUpdateTranslator.U1DArrayEvaluator(regionId, uninterpretedAddressesMapping) - - resolvedInputFields = resolvedInputFields.put(field, result) - result + val regionId = UInputFieldId(field, sort) + val evaluator = regionEvaluatorProvider.provide(regionId) + resolvedInputFields = resolvedInputFields.put(field, evaluator) + evaluator } as URegionEvaluator regionEvaluator.write(ref, valueToWrite) @@ -272,7 +240,7 @@ class UHeapModel( value: UExpr, guard: UBoolExpr, ) { - // Since all values in the model are interpreted, we can check the exact guard value. + // Since all values in the model are interpreted, we can check the exact guard8 value. when { guard.isFalse -> return else -> require(guard.isTrue) @@ -282,17 +250,16 @@ class UHeapModel( // have concrete addresses. Moreover, the model known only about input values // which have addresses less or equal than INITIAL_INPUT_ADDRESS require(index is KInterpretedValue) - require(ref is UConcreteHeapRef && ref.address < INITIAL_INPUT_ADDRESS) + require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) val valueToWrite = value.asExpr(elementSort) @Suppress("UNCHECKED_CAST") val regionEvaluator = resolvedInputArrays.getOrElse(type) { val regionId = UInputArrayId(type, elementSort) - val result = U2DArrayUpdateTranslator.U2DArrayEvaluator(regionId, uninterpretedAddressesMapping) - - resolvedInputArrays = resolvedInputArrays.put(type, result) - result + val evaluator = regionEvaluatorProvider.provide(regionId) + resolvedInputArrays = resolvedInputArrays.put(type, evaluator) + evaluator } as URegionEvaluator, Sort> regionEvaluator.write(ref to index, valueToWrite) @@ -303,15 +270,16 @@ class UHeapModel( // have concrete addresses. Moreover, the model known only about input values // which have addresses less or equal than INITIAL_INPUT_ADDRESS require(size is KInterpretedValue) - require(ref is UConcreteHeapRef && ref.address < INITIAL_INPUT_ADDRESS) + require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - resolvedInputLengths.getOrElse(arrayType) { + val reginEvaluator = resolvedInputLengths.getOrElse(arrayType) { val regionId = UInputArrayLengthId(arrayType, ctx.sizeSort) - val result = U1DArrayUpdateTranslator.U1DArrayEvaluator(regionId, uninterpretedAddressesMapping) + val evaluator = regionEvaluatorProvider.provide(regionId) + resolvedInputLengths = resolvedInputLengths.put(arrayType, evaluator) + evaluator + } - resolvedInputLengths = resolvedInputLengths.put(arrayType, result) - result - }.write(ref, size) + reginEvaluator.write(ref, size) } override fun memcpy( @@ -343,11 +311,17 @@ class UHeapModel( override fun clone(): UHeap, USizeExpr, Field, ArrayType, UBoolExpr> = UHeapModel( ctx, - resolvedInputFields, - resolvedInputArrays, - resolvedInputLengths, - uninterpretedAddressesMapping + nullRef, + regionEvaluatorProvider, + resolvedInputFields.mapValues { evaluator -> evaluator.clone() }, + resolvedInputArrays.mapValues { evaluator -> evaluator.clone() }, + resolvedInputLengths.mapValues { evaluator -> evaluator.clone() }, ) + override fun nullRef(): UHeapRef = nullRef + override fun toMutableHeap() = clone() -} \ No newline at end of file +} + +inline private fun PersistentMap.mapValues(crossinline mapper: (V) -> V): PersistentMap = + mutate { old -> old.replaceAll { _, value -> mapper(value) } } diff --git a/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt b/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt index c57be7432b..cc7f0c1a7d 100644 --- a/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt +++ b/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt @@ -129,7 +129,7 @@ class UPinpointUpdateNode( override fun hashCode(): Int = key.hashCode() * 31 + guard.hashCode() // Ignores value - override fun toString(): String = "{$key <- $value | $guard}" + override fun toString(): String = "{$key <- $value}".takeIf { guard.isTrue } ?: "{$key <- $value | $guard}" } /** @@ -313,7 +313,7 @@ class URangedUpdateNode, Array override fun hashCode(): Int = (17 * fromKey.hashCode() + toKey.hashCode()) * 31 + guard.hashCode() override fun toString(): String { - return "{[$fromKey..$toKey] <- $region[keyConv($fromKey)..keyConv($toKey)] | $guard}" + return "{[$fromKey..$toKey] <- $region[keyConv($fromKey)..keyConv($toKey)]" + ("}".takeIf { guard.isTrue } ?: " | $guard}") } } diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index 69df6bd48b..296a91f05c 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -361,7 +361,7 @@ internal class CompositionTest { ).write(fstIndex, 1.toBv(), guard = trueExpr) .write(sndIndex, 2.toBv(), guard = trueExpr) - val regionId = UAllocatedArrayId(arrayType, address, bv32Sort) + val regionId = UAllocatedArrayId(arrayType, address, bv32Sort, bv32Sort.sampleValue()) val regionArray = UAllocatedArrayRegion( regionId, updates, @@ -416,7 +416,7 @@ internal class CompositionTest { // An empty region with one write in it val region = UInputFieldRegion( - UInputFieldRegionId(field, bv32Sort), + UInputFieldId(field, bv32Sort), updates, instantiator = { key, region -> mkInputFieldReading(region, key) } ).write(aAddress, 43.toBv(), guard = trueExpr) diff --git a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt new file mode 100644 index 0000000000..ba5a45f099 --- /dev/null +++ b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt @@ -0,0 +1,67 @@ +package org.usvm + +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.ksmt.utils.mkConst +import org.usvm.UAddressCounter.Companion.NULL_ADDRESS +import kotlin.test.assertSame +import kotlinx.collections.immutable.persistentMapOf + +class ModelDecodingTest { + private lateinit var ctx: UContext + + @BeforeEach + fun initializeContext() { + ctx = UContext() + } + + @Test + @Suppress("UNUSED_VARIABLE") + fun testVeryTrickyCase() = with(ctx) { + val translator = UExprTranslator(this) + + val heapModel = + UHeapModel(this, mkConcreteHeapRef(0), mockk(), persistentMapOf(), persistentMapOf(), persistentMapOf()) + + val stackModel = URegistersStackModel(mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) + + val model = UModelBase(this, stackModel, heapModel, mockk(), mockk()) + + + val region = emptyAllocatedArrayRegion(mockk(), 1, bv32Sort) { key, reg -> + ctx.mkAllocatedArrayReading(reg, key) + } + .write(0.toBv(), 0.toBv(), trueExpr) + .write(1.toBv(), 1.toBv(), trueExpr) + .write(mkRegisterReading(1, sizeSort), 2.toBv(), trueExpr) + .write(mkRegisterReading(2, sizeSort), 3.toBv(), trueExpr) + val reading = region.read(mkRegisterReading(0, sizeSort)) + + val expr = model.eval(reading) + assertSame(mkBv(2), expr) + } + + @Test + @Suppress("UNUSED_VARIABLE") + fun testTrickyCase() = with(ctx) { + val translator = UExprTranslator(this) + + val heapModel = + UHeapModel(this, mkConcreteHeapRef(0), mockk(), persistentMapOf(), persistentMapOf(), persistentMapOf()) + + val stackModel = URegistersStackModel(mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) + + val model = UModelBase(this, stackModel, heapModel, mockk(), mockk()) + + + val region = emptyAllocatedArrayRegion(mockk(), 1, addressSort) { key, reg -> + ctx.mkAllocatedArrayReading(reg, key) + } + val reading = region.read(mkRegisterReading(0, sizeSort)) + + val expr = model.eval(reading) + assertSame(ctx.mkConcreteHeapRef(NULL_ADDRESS), expr) + } +} diff --git a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt index 73971160ac..a1a2c7ea90 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt @@ -2,3 +2,4 @@ package org.usvm typealias Field = java.lang.reflect.Field typealias ArrayType = kotlin.reflect.KClass<*> +typealias Method = kotlin.reflect.KFunction<*> diff --git a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt index 6b06ef0ea2..f4b90093af 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt @@ -3,6 +3,7 @@ package org.usvm import io.mockk.mockk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.Test import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.utils.mkConst @@ -18,7 +19,6 @@ class TranslationTest { private lateinit var valueArrayDescr: ArrayType private lateinit var addressArrayDescr: ArrayType - @BeforeEach fun initializeContext() { ctx = UContext() @@ -118,7 +118,8 @@ class TranslationTest { assertSame(expected, translated) } - @Test +// @Test + @RepeatedTest(30) fun testTranslateArrayCopy() = with(ctx) { var region = emptyInputArrayRegion(valueArrayDescr, bv32Sort) { (ref, idx), reg -> mkInputArrayReading(reg, ref, idx) @@ -147,8 +148,9 @@ class TranslationTest { val reading = concreteRegion.read(idx) + val key = region.regionId.keyMapper(translator)(keyConverter.convert(translator.translate(idx))) val innerReading = - translator.translateRegionReading(region, keyConverter.convert(translator.translate(idx))) + translator.translateRegionReading(region, key) val guard = translator.translate((mkBvSignedLessOrEqualExpr(mkBv(0), idx)) and mkBvSignedLessOrEqualExpr(idx, mkBv(5))) val expected = mkIte(guard, innerReading, bv32Sort.sampleValue()) From 2c5c4786df3a934817d30a5a7974b6e39a27519c Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 10 Apr 2023 17:34:03 +0300 Subject: [PATCH 08/28] Refactor everything --- .../src/main/kotlin/org/usvm/Composition.kt | 6 ++ usvm-core/src/main/kotlin/org/usvm/Context.kt | 14 ++- .../main/kotlin/org/usvm/ExprTranslator.kt | 48 +++++----- usvm-core/src/main/kotlin/org/usvm/Heap.kt | 2 +- .../src/main/kotlin/org/usvm/MemoryRegions.kt | 2 + .../src/main/kotlin/org/usvm/MemoryUpdates.kt | 17 ++-- usvm-core/src/main/kotlin/org/usvm/Mocks.kt | 1 - usvm-core/src/main/kotlin/org/usvm/Model.kt | 12 ++- .../main/kotlin/org/usvm/RegionEvaluator.kt | 3 +- .../src/main/kotlin/org/usvm/RegionIds.kt | 10 ++- .../main/kotlin/org/usvm/RegionTranslator.kt | 2 - .../main/kotlin/org/usvm/RegistersStack.kt | 3 - usvm-core/src/main/kotlin/org/usvm/Solver.kt | 3 +- .../main/kotlin/org/usvm/TranslatorBuilder.kt | 17 ++-- .../src/main/kotlin/org/usvm/UModelDecoder.kt | 89 +++++++++---------- .../src/main/kotlin/org/usvm/UpdateNodes.kt | 3 +- .../test/kotlin/org/usvm/ModelDecodingTest.kt | 34 ++++--- .../kotlin/org/usvm/UpdatesIteratorTest.kt | 1 - 18 files changed, 142 insertions(+), 125 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/Composition.kt b/usvm-core/src/main/kotlin/org/usvm/Composition.kt index ac74721efb..9a917f1ba2 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Composition.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Composition.kt @@ -1,5 +1,11 @@ package org.usvm +import org.ksmt.expr.KEqExpr +import org.ksmt.expr.KExpr +import org.ksmt.sort.KBoolSort +import org.ksmt.sort.KSort +import org.ksmt.utils.asExpr + @Suppress("MemberVisibilityCanBePrivate") open class UComposer( ctx: UContext, diff --git a/usvm-core/src/main/kotlin/org/usvm/Context.kt b/usvm-core/src/main/kotlin/org/usvm/Context.kt index 6f87c6f452..826535309e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Context.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Context.kt @@ -4,7 +4,10 @@ import org.ksmt.KAst import org.ksmt.KContext import org.ksmt.expr.KExpr import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue +import org.ksmt.sort.KBoolSort +import org.ksmt.sort.KSort import org.ksmt.sort.KUninterpretedSort +import org.ksmt.utils.asExpr import org.ksmt.utils.cast @Suppress("LeakingThis") @@ -44,6 +47,13 @@ open class UContext( * * @return the new equal rewritten expression without [UConcreteHeapRef]s */ + override fun mkEq(lhs: KExpr, rhs: KExpr, order: Boolean): KExpr = + if (lhs.sort == addressSort) { + mkHeapRefEq(lhs.asExpr(addressSort), rhs.asExpr(addressSort)) + } else { + super.mkEq(lhs, rhs, order) + } + fun mkHeapRefEq(lhs: UHeapRef, rhs: UHeapRef): UBoolExpr = when { // fast checks @@ -77,10 +87,6 @@ open class UContext( } } - @Suppress("UNUSED_PARAMETER") - @Deprecated("Use mkHeapRefEq instead.", ReplaceWith("this.mkHeapRefEq(lhs, rhs)"), level = DeprecationLevel.ERROR) - fun mkEq(lhs: UHeapRef, rhs: UHeapRef): Nothing = error("Use mkHeapRefEq instead.") - private val uConcreteHeapRefCache = mkAstInterner() fun mkConcreteHeapRef(address: UConcreteHeapAddress): UConcreteHeapRef = uConcreteHeapRefCache.createIfContextActive { diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index d09937527a..f9b750ae37 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -5,38 +5,23 @@ import org.ksmt.sort.KArraySort import org.ksmt.sort.KArraySortBase import org.ksmt.utils.mkConst -interface URegionTranslatorProvider : URegionIdVisitor> { - fun provide( - regionId: URegionId, - ): URegionTranslator, Key, Sort, *> { - @Suppress("UNCHECKED_CAST") - return apply(regionId) as URegionTranslator, Key, Sort, *> - } -} - -typealias URegionIdInitialValueProvider = URegionIdVisitor> - open class UExprTranslator constructor( override val ctx: UContext, ) : UExprTransformer(ctx), URegionTranslatorProvider { open fun translate(expr: UExpr): UExpr = apply(expr) - // TODO: why do we have this function in UExprTransformer? - override fun transform(expr: USymbol): UExpr { - error("You must override `transform` function in org.usvm.UExprTranslator for ${expr::class}") - } + override fun transform(expr: USymbol): UExpr = + error("You must override `transform` function in UExprTranslator for ${expr::class}") override fun transform(expr: URegisterReading): UExpr { val registerConst = expr.sort.mkConst("r${expr.idx}") return registerConst } - // TODO: why do we have this function in UExprTransformer? override fun transform(expr: UHeapReading<*, *, *>): UExpr = error("You must override `transform` function in UExprTranslator for ${expr::class}") - // TODO: why do we have this function in UExprTransformer? override fun transform(expr: UMockSymbol): UExpr = error("You must override `transform` function in UExprTranslator for ${expr::class}") @@ -50,13 +35,11 @@ open class UExprTranslator constructor( return const } - override fun transform(expr: UConcreteHeapRef): UExpr { + override fun transform(expr: UConcreteHeapRef): UExpr = error("Unexpected UConcreteHeapRef $expr in UExprTranslator, that has to be impossible by construction!") - } - override fun transform(expr: UIsExpr): UBoolExpr { + override fun transform(expr: UIsExpr): UBoolExpr = error("Unexpected UIsExpr $expr in UExprTranslator, that has to be impossible by construction!") - } override fun transform(expr: UInputArrayLengthReading): USizeExpr = transformExprAfterTransformed(expr, expr.address) { address -> @@ -82,10 +65,12 @@ open class UExprTranslator constructor( region: UMemoryRegion, Key, Sort>, key: Key, ): UExpr { - val translator = provide(region.regionId) - return translator.translateReading(region, key) + val regionTranslator = provide(region.regionId) + return regionTranslator.translateReading(region, key) } + // these functions implements URegionTranslatorProvider + override fun visit( regionId: UInputFieldId, ): URegionTranslator, UHeapRef, Sort, *> { @@ -125,13 +110,24 @@ open class UExprTranslator constructor( val regionIdInitialValueProvider = URegionIdInitialValueProviderBase(onDefaultValuePresent = { translate(it) }) } +interface URegionTranslatorProvider : URegionIdVisitor> { + fun provide( + regionId: URegionId, + ): URegionTranslator, Key, Sort, *> { + @Suppress("UNCHECKED_CAST") + return apply(regionId) as URegionTranslator, Key, Sort, *> + } +} + +typealias URegionIdInitialValueProvider = URegionIdVisitor> + class URegionIdInitialValueProviderBase( val onDefaultValuePresent: (UExpr<*>) -> UExpr<*>, ) : URegionIdVisitor>> { override fun visit(regionId: UInputFieldId): UExpr> { require(regionId.defaultValue == null) return with(regionId.sort.uctx) { - mkArraySort(addressSort, regionId.sort).mkConst(regionId.toString()) + mkArraySort(addressSort, regionId.sort).mkConst(regionId.toString()) // TODO: replace toString } } @@ -150,14 +146,14 @@ class URegionIdInitialValueProviderBase( override fun visit(regionId: UInputArrayId): UExpr> { require(regionId.defaultValue == null) return with(regionId.sort.uctx) { - mkArraySort(addressSort, sizeSort, regionId.sort).mkConst(regionId.toString()) + mkArraySort(addressSort, sizeSort, regionId.sort).mkConst(regionId.toString()) // TODO: replace toString } } override fun visit(regionId: UInputArrayLengthId): UExpr> { require(regionId.defaultValue == null) return with(regionId.sort.uctx) { - mkArraySort(addressSort, sizeSort).mkConst(regionId.toString()) + mkArraySort(addressSort, sizeSort).mkConst(regionId.toString()) // TODO: replace toString } } } \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/Heap.kt b/usvm-core/src/main/kotlin/org/usvm/Heap.kt index 74847d3d45..ed047632d2 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Heap.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Heap.kt @@ -99,7 +99,7 @@ data class URegionHeap( private val ctx: UContext, private var lastAddress: UAddressCounter = UAddressCounter(), private var allocatedFields: PersistentMap, UExpr> = persistentMapOf(), - private var inputFields: PersistentMap> = persistentMapOf(), + private var inputFields: PersistentMap> = persistentMapOf(), private var allocatedArrays: PersistentMap> = persistentMapOf(), private var inputArrays: PersistentMap> = persistentMapOf(), private var allocatedLengths: PersistentMap = persistentMapOf(), diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt index 212eaad824..a0779ba850 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt @@ -28,7 +28,9 @@ data class UMemoryRegion, Key, Sort : USort> val updates: UMemoryUpdates, private val instantiator: UInstantiator, ) { + // to save memory usage val sort: Sort get() = regionId.sort + // if we replace it with get(), we have to check it every time in read function val defaultValue = regionId.defaultValue private fun read(key: Key, updates: UMemoryUpdates): UExpr { diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt index ec1b84bc35..2125241729 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt @@ -75,7 +75,7 @@ interface UMemoryUpdates : Sequence> { //region Flat memory updates class UFlatUpdates private constructor( - internal val node: FlatNode?, + internal val node: UFlatUpdatesNode?, private val symbolicEq: (Key, Key) -> UBoolExpr, private val concreteCmp: (Key, Key) -> Boolean, private val symbolicCmp: (Key, Key) -> UBoolExpr, @@ -84,9 +84,9 @@ class UFlatUpdates private constructor( symbolicEq: (Key, Key) -> UBoolExpr, concreteCmp: (Key, Key) -> Boolean, symbolicCmp: (Key, Key) -> UBoolExpr, - ) : this(null, symbolicEq, concreteCmp, symbolicCmp) + ) : this(node = null, symbolicEq, concreteCmp, symbolicCmp) - internal data class FlatNode( + internal data class UFlatUpdatesNode( val update: UUpdateNode, val next: UFlatUpdates, ) @@ -95,7 +95,7 @@ class UFlatUpdates private constructor( override fun write(key: Key, value: UExpr, guard: UBoolExpr): UFlatUpdates = UFlatUpdates( - FlatNode(UPinpointUpdateNode(key, value, symbolicEq, guard), this), + UFlatUpdatesNode(UPinpointUpdateNode(key, value, symbolicEq, guard), this), symbolicEq, concreteCmp, symbolicCmp @@ -108,7 +108,10 @@ class UFlatUpdates private constructor( keyConverter: UMemoryKeyConverter, guard: UBoolExpr, ): UMemoryUpdates = UFlatUpdates( - FlatNode(URangedUpdateNode(fromKey, toKey, fromRegion, concreteCmp, symbolicCmp, keyConverter, guard), this), + UFlatUpdatesNode( + URangedUpdateNode(fromKey, toKey, fromRegion, concreteCmp, symbolicCmp, keyConverter, guard), + this + ), symbolicEq, concreteCmp, symbolicCmp @@ -132,7 +135,7 @@ class UFlatUpdates private constructor( return this } - return UFlatUpdates(FlatNode(splitNode, splitNext), symbolicEq, concreteCmp, symbolicCmp) + return UFlatUpdates(UFlatUpdatesNode(splitNode, splitNext), symbolicEq, concreteCmp, symbolicCmp) } override fun map( @@ -150,7 +153,7 @@ class UFlatUpdates private constructor( } // Otherwise, construct a new one using the mapped values - return UFlatUpdates(FlatNode(mappedNode, mappedNext), symbolicEq, concreteCmp, symbolicCmp) + return UFlatUpdates(UFlatUpdatesNode(mappedNode, mappedNext), symbolicEq, concreteCmp, symbolicCmp) } /** diff --git a/usvm-core/src/main/kotlin/org/usvm/Mocks.kt b/usvm-core/src/main/kotlin/org/usvm/Mocks.kt index bc9a652cd4..10d150bb68 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Mocks.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Mocks.kt @@ -4,7 +4,6 @@ import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf -import org.ksmt.solver.KModel import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.utils.asExpr diff --git a/usvm-core/src/main/kotlin/org/usvm/Model.kt b/usvm-core/src/main/kotlin/org/usvm/Model.kt index 115e2f5f24..43a2f20d0e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Model.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Model.kt @@ -1,7 +1,5 @@ package org.usvm -import org.ksmt.expr.rewrite.KExprSubstitutor - interface UModel { fun eval(expr: UExpr): UExpr } @@ -9,11 +7,11 @@ interface UModel { // TODO: Eval visitor open class UModelBase( - private val ctx: UContext, - val stack: URegistersStackModel, - val heap: UReadOnlySymbolicHeap, - val types: UTypeModel, - val mocks: UMockEvaluator + ctx: UContext, + stack: URegistersStackModel, + heap: UReadOnlySymbolicHeap, + types: UTypeModel, + mocks: UMockEvaluator ) : UModel { private val composer = UComposer(ctx, stack, heap, types, mocks) diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt index ef2550c8cf..52d7911182 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt @@ -8,7 +8,6 @@ import org.ksmt.solver.KModel import org.ksmt.sort.KArray2Sort import org.ksmt.sort.KArraySort import org.ksmt.utils.cast -import org.usvm.UModelDecoderBase.Companion.mapAddress interface URegionEvaluator { fun select(key: Key): UExpr @@ -163,7 +162,7 @@ interface URegionEvaluatorProvider { ): URegionEvaluator } -open class URegionEvaluatorProviderFromKModel( +open class URegionEvaluatorFromKModelProvider( private val model: KModel, private val mapping: Map, UConcreteHeapRef>, private val regionIdInitialValueProvider: URegionIdInitialValueProvider, diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt index 4f21c5e741..3692144b0e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt @@ -1,6 +1,5 @@ package org.usvm -import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.utils.asExpr /** @@ -121,8 +120,13 @@ data class UAllocatedArrayId internal constructor( transformer: UExprTransformer, ): KeyMapper = { transformer.apply(it) } - override fun map(composer: UComposer): UAllocatedArrayId = - copy(defaultValue = composer.compose(defaultValue)) + override fun map(composer: UComposer): UAllocatedArrayId { + val composedDefaultValue = composer.compose(defaultValue) + if (composedDefaultValue === defaultValue) { + return this + } + return copy(defaultValue = composedDefaultValue) + } // we don't include arrayType into hashcode and equals, because [address] already defines unambiguously override fun equals(other: Any?): Boolean { diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt index 44f73a3040..3fa3cc5021 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt @@ -18,8 +18,6 @@ class URegionTranslator, Key, Sort : USort, R return updateTranslator.select(translated, key) } - fun initialValue(): Result = updateTranslator.initialValue() - private val cache = IdentityHashMap, Result>() private fun translate(region: UMemoryRegion): Result = diff --git a/usvm-core/src/main/kotlin/org/usvm/RegistersStack.kt b/usvm-core/src/main/kotlin/org/usvm/RegistersStack.kt index 3b5561efe2..d8847e234e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegistersStack.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegistersStack.kt @@ -54,9 +54,6 @@ class URegistersStack( return URegistersStack(ctx, newStack) } - @Suppress("UNUSED_PARAMETER") - fun decode(model: KModel): URegistersStackModel = TODO() - override fun eval( registerIndex: Int, sort: Sort, diff --git a/usvm-core/src/main/kotlin/org/usvm/Solver.kt b/usvm-core/src/main/kotlin/org/usvm/Solver.kt index 4dc7c0a29e..e6573a3e93 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Solver.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Solver.kt @@ -7,7 +7,6 @@ import org.usvm.UExprTranslator import org.usvm.UMemoryBase import org.usvm.UModelBase import org.usvm.UModelDecoder -import org.usvm.UModelDecoderBase import org.usvm.UPathCondition sealed interface USolverResult @@ -58,4 +57,4 @@ open class USolverBase( val uModel = evaluator.decode(memory, model) return USolverSat(uModel) } -} \ No newline at end of file +} diff --git a/usvm-core/src/main/kotlin/org/usvm/TranslatorBuilder.kt b/usvm-core/src/main/kotlin/org/usvm/TranslatorBuilder.kt index 84fd54b537..d8d2490cbd 100644 --- a/usvm-core/src/main/kotlin/org/usvm/TranslatorBuilder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/TranslatorBuilder.kt @@ -2,7 +2,6 @@ package org.usvm import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.utils.cast -import org.usvm.UModelDecoderBase.Companion.mapAddress private class UCachingExprTranslator( ctx: UContext, @@ -22,9 +21,9 @@ private class UCachingExprTranslator( super.transform(expr) }.cast() - val nullRef = super.translate(ctx.nullRef) + val translatedNullRef = super.translate(ctx.nullRef) - override fun transform(expr: UNullRef): UExpr = nullRef + override fun transform(expr: UNullRef): UExpr = translatedNullRef val regionIdToTranslator = mutableMapOf, URegionTranslator, *, *, *>>() @@ -35,10 +34,10 @@ private class UCachingExprTranslator( }.cast() } -private class UCachingRegionEvaluatorProvider( +private class URegionEvaluatorForHeapModelProvider( val mapping: AddressesMapping, translatedRegionIds: Set>, - regionEvaluatorProvider: URegionEvaluatorProviderFromKModel, + regionEvaluatorProvider: URegionEvaluatorFromKModelProvider, ) : URegionEvaluatorProvider, URegionIdVisitor> { private val evaluatorsForTranslatedRegions: MutableMap, URegionEvaluator<*, *>> @@ -50,7 +49,7 @@ private class UCachingRegionEvaluatorProvider( } override fun provide(regionId: URegionId): URegionEvaluator = - evaluatorsForTranslatedRegions.getOrPut(regionId) { + evaluatorsForTranslatedRegions.getOrElse(regionId) { apply(regionId) }.cast() @@ -94,11 +93,11 @@ fun buildDefaultTranslatorAndDecoder( val decoder = UModelDecoderBase( translator.registerIdxToTranslated, translator.indexedMethodReturnValueToTranslated, - translator.nullRef + translator.translatedNullRef ) { model, mapping -> val regionEvaluatorProviderFromKModel = - URegionEvaluatorProviderFromKModel(model, mapping, translator.regionIdInitialValueProvider) - UCachingRegionEvaluatorProvider( + URegionEvaluatorFromKModelProvider(model, mapping, translator.regionIdInitialValueProvider) + URegionEvaluatorForHeapModelProvider( mapping, translator.regionIdToTranslator.keys, regionEvaluatorProviderFromKModel diff --git a/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt index 188a1ba15b..6e1457e1a5 100644 --- a/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt @@ -21,10 +21,10 @@ typealias AddressesMapping = Map, UConcreteHeapRef> open class UModelDecoderBase( protected val registerIdxToTranslated: Map>, protected val indexedMethodReturnValueToTranslated: Map, UExpr<*>>, - protected val nullRef: UExpr, + protected val translatedNullRef: UExpr, protected val regionEvaluatorProviderBuilder: (KModel, AddressesMapping) -> URegionEvaluatorProvider, ) : UModelDecoder, UModelBase> { - private val ctx: UContext = nullRef.uctx + private val ctx: UContext = translatedNullRef.uctx /** * Build a mapping from instances of an uninterpreted [UAddressSort] @@ -33,7 +33,7 @@ open class UModelDecoderBase( */ private fun buildMapping(model: KModel): AddressesMapping { // Null is a special value that we want to translate in any case. - val interpretedNullRef = model.eval(nullRef, isComplete = true) + val interpretedNullRef = model.eval(translatedNullRef, isComplete = true) val universe = model.uninterpretedSortUniverse(ctx.addressSort) ?: return emptyMap() // All the numbers are enumerated from the INITIAL_INPUT_ADDRESS to the Int.MIN_VALUE @@ -53,18 +53,6 @@ open class UModelDecoderBase( return result } - /** - * Constructs a [UHeapModel] for a heap by provided [model] and [addressesMapping]. - */ - @Suppress("UNCHECKED_CAST", "SafeCastWithReturn") - private fun decodeHeap( - model: KModel, - addressesMapping: AddressesMapping, - ): UHeapModel { - val regionEvaluator = regionEvaluatorProviderBuilder(model, addressesMapping) - return UHeapModel(ctx, addressesMapping.getValue(ctx.nullRef), regionEvaluator, persistentMapOf(), persistentMapOf(), persistentMapOf()) - } - override fun decode( memory: UMemoryBase, model: KModel, @@ -79,17 +67,35 @@ open class UModelDecoderBase( return UModelBase(ctx, stack, heap, types, mocks) } - private fun decodeStack(model: KModel, mapping: Map, UConcreteHeapRef>): URegistersStackModel { - val registers = registerIdxToTranslated.replaceUninterpretedConsts(model, mapping) + private fun decodeStack(model: KModel, addressesMapping: AddressesMapping): URegistersStackModel { + val registers = registerIdxToTranslated.replaceUninterpretedConsts(model, addressesMapping) return URegistersStackModel(registers) } + /** + * Constructs a [UHeapModel] for a heap by provided [model] and [addressesMapping]. + */ + @Suppress("UNCHECKED_CAST", "SafeCastWithReturn") + private fun decodeHeap( + model: KModel, + addressesMapping: AddressesMapping, + ): UHeapModel { + val regionEvaluator = regionEvaluatorProviderBuilder(model, addressesMapping) + return UHeapModel( + addressesMapping.getValue(translatedNullRef), + regionEvaluator, + persistentMapOf(), + persistentMapOf(), + persistentMapOf() + ) + } + private fun decodeMocker( model: KModel, - mapping: Map, UConcreteHeapRef>, + addressesMapping: AddressesMapping, ): UIndexedMockModel { - val values = indexedMethodReturnValueToTranslated.replaceUninterpretedConsts(model, mapping) + val values = indexedMethodReturnValueToTranslated.replaceUninterpretedConsts(model, addressesMapping) return UIndexedMockModel(values) } @@ -113,20 +119,6 @@ open class UModelDecoderBase( return values } - companion object { - /** - * If [this] value is an instance of address expression, returns - * an expression with a corresponding concrete address, otherwise - * returns [this] unchanched. - */ - fun UExpr.mapAddress( - mapping: Map, UConcreteHeapRef>, - ): UExpr = if (sort == uctx.addressSort) { - mapping.getValue(asExpr(uctx.addressSort)).asExpr(sort) - } else { - this - } - } } class URegistersStackModel(private val registers: Map>) : URegistersStackEvaluator { @@ -137,7 +129,6 @@ class URegistersStackModel(private val registers: Map>) : } class UHeapModel( - private val ctx: UContext, private val nullRef: UExpr, private val regionEvaluatorProvider: URegionEvaluatorProvider, private var resolvedInputFields: PersistentMap>, @@ -152,7 +143,7 @@ class UHeapModel( @Suppress("UNCHECKED_CAST") val regionEvaluator = resolvedInputFields.getOrElse(field) { - val regionId = UInputFieldId(field, ctx.sizeSort) + val regionId = UInputFieldId(field, sort) val evaluator = regionEvaluatorProvider.provide(regionId) resolvedInputFields = resolvedInputFields.put(field, evaluator) evaluator @@ -192,7 +183,7 @@ class UHeapModel( require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) val regionEvaluator = resolvedInputLengths.getOrElse(arrayType) { - val regionId = UInputArrayLengthId(arrayType, ctx.sizeSort) + val regionId = UInputArrayLengthId(arrayType, ref.uctx.sizeSort) val evaluator = regionEvaluatorProvider.provide(regionId) resolvedInputLengths = resolvedInputLengths.put(arrayType, evaluator) evaluator @@ -273,7 +264,7 @@ class UHeapModel( require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) val reginEvaluator = resolvedInputLengths.getOrElse(arrayType) { - val regionId = UInputArrayLengthId(arrayType, ctx.sizeSort) + val regionId = UInputArrayLengthId(arrayType, ref.uctx.sizeSort) val evaluator = regionEvaluatorProvider.provide(regionId) resolvedInputLengths = resolvedInputLengths.put(arrayType, evaluator) evaluator @@ -291,18 +282,14 @@ class UHeapModel( fromDstIdx: USizeExpr, toDstIdx: USizeExpr, guard: UBoolExpr, - ) { - TODO("Not yet implemented") - } + ): Unit = error("Illegal operation for a model") override fun memset( ref: UHeapRef, type: ArrayType, sort: Sort, contents: Sequence>, - ) { - TODO("Not yet implemented") - } + ): Unit = error("Illegal operation for a model") override fun allocate() = error("Illegal operation for a model") @@ -310,11 +297,10 @@ class UHeapModel( override fun clone(): UHeap, USizeExpr, Field, ArrayType, UBoolExpr> = UHeapModel( - ctx, nullRef, regionEvaluatorProvider, resolvedInputFields.mapValues { evaluator -> evaluator.clone() }, - resolvedInputArrays.mapValues { evaluator -> evaluator.clone() }, + resolvedInputArrays.mapValues { evaluator -> evaluator.clone() }, resolvedInputLengths.mapValues { evaluator -> evaluator.clone() }, ) @@ -325,3 +311,16 @@ class UHeapModel( inline private fun PersistentMap.mapValues(crossinline mapper: (V) -> V): PersistentMap = mutate { old -> old.replaceAll { _, value -> mapper(value) } } + +/** + * If [this] value is an instance of address expression, returns + * an expression with a corresponding concrete address, otherwise + * returns [this] unchanched. + */ +fun UExpr.mapAddress( + addressesMapping: AddressesMapping, +): UExpr = if (sort == uctx.addressSort) { + addressesMapping.getValue(asExpr(uctx.addressSort)).asExpr(sort) +} else { + this +} \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt b/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt index cc7f0c1a7d..99bbd67fca 100644 --- a/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt +++ b/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt @@ -313,7 +313,8 @@ class URangedUpdateNode, Array override fun hashCode(): Int = (17 * fromKey.hashCode() + toKey.hashCode()) * 31 + guard.hashCode() override fun toString(): String { - return "{[$fromKey..$toKey] <- $region[keyConv($fromKey)..keyConv($toKey)]" + ("}".takeIf { guard.isTrue } ?: " | $guard}") + return "{[$fromKey..$toKey] <- $region[keyConv($fromKey)..keyConv($toKey)]" + + ("}".takeIf { guard.isTrue } ?: " | $guard}") } } diff --git a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt index ba5a45f099..3f782d6bcc 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt @@ -18,12 +18,9 @@ class ModelDecodingTest { } @Test - @Suppress("UNUSED_VARIABLE") fun testVeryTrickyCase() = with(ctx) { - val translator = UExprTranslator(this) - val heapModel = - UHeapModel(this, mkConcreteHeapRef(0), mockk(), persistentMapOf(), persistentMapOf(), persistentMapOf()) + UHeapModel(mkConcreteHeapRef(0), mockk(), persistentMapOf(), persistentMapOf(), persistentMapOf()) val stackModel = URegistersStackModel(mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) @@ -44,24 +41,39 @@ class ModelDecodingTest { } @Test - @Suppress("UNUSED_VARIABLE") fun testTrickyCase() = with(ctx) { - val translator = UExprTranslator(this) - val heapModel = - UHeapModel(this, mkConcreteHeapRef(0), mockk(), persistentMapOf(), persistentMapOf(), persistentMapOf()) + UHeapModel(mkConcreteHeapRef(0), mockk(), persistentMapOf(), persistentMapOf(), persistentMapOf()) - val stackModel = URegistersStackModel(mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) + val stackModel = URegistersStackModel(mapOf(0 to mkBv(0), 1 to mkBv(0), 2 to mkBv(2))) val model = UModelBase(this, stackModel, heapModel, mockk(), mockk()) val region = emptyAllocatedArrayRegion(mockk(), 1, addressSort) { key, reg -> - ctx.mkAllocatedArrayReading(reg, key) + mkAllocatedArrayReading(reg, key) } val reading = region.read(mkRegisterReading(0, sizeSort)) val expr = model.eval(reading) - assertSame(ctx.mkConcreteHeapRef(NULL_ADDRESS), expr) + assertSame(mkConcreteHeapRef(NULL_ADDRESS), expr) + } + + @Test + @Suppress("UNUSED_VARIABLE") + fun testHeapRefEq() = with(ctx) { + val translator = UExprTranslator(this) + + val heapModel = + UHeapModel(mkConcreteHeapRef(0), mockk(), persistentMapOf(), persistentMapOf(), persistentMapOf()) + + val stackModel = URegistersStackModel(mapOf(0 to mkConcreteHeapRef(-1), 1 to mkConcreteHeapRef(-2))) + + val model = UModelBase(this, stackModel, heapModel, mockk(), mockk()) + + val heapRefEvalEq = mkHeapRefEq(mkRegisterReading(0, addressSort), mkRegisterReading(1, addressSort)) + + val expr = model.eval(heapRefEvalEq) + assertSame(falseExpr, expr) } } diff --git a/usvm-core/src/test/kotlin/org/usvm/UpdatesIteratorTest.kt b/usvm-core/src/test/kotlin/org/usvm/UpdatesIteratorTest.kt index d8fbfa2528..a64bd5ae12 100644 --- a/usvm-core/src/test/kotlin/org/usvm/UpdatesIteratorTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/UpdatesIteratorTest.kt @@ -1,6 +1,5 @@ package org.usvm -import com.microsoft.z3.IntSort import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.usvm.util.SetRegion From 5b53b2474cabd01e32060236cf9905f9814396cf Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 10 Apr 2023 17:49:25 +0300 Subject: [PATCH 09/28] Fix: `mkHeapRefEq` and `KZ3Solver` in test --- usvm-core/src/main/kotlin/org/usvm/Context.kt | 2 +- .../src/test/kotlin/org/usvm/TranslationTest.kt | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/Context.kt b/usvm-core/src/main/kotlin/org/usvm/Context.kt index 826535309e..c0089a7720 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Context.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Context.kt @@ -76,7 +76,7 @@ open class UContext( } if (symbolicRefLhs != null && symbolicRefRhs != null) { - val refsEq = symbolicRefLhs.expr eq symbolicRefRhs.expr + val refsEq = super.mkEq(symbolicRefLhs.expr, symbolicRefRhs.expr, order = true) // mkAnd instead of mkAndNoFlat here is OK val conjunct = mkAnd(symbolicRefLhs.guard, symbolicRefRhs.guard, refsEq) conjuncts += conjunct diff --git a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt index f4b90093af..e08483bf51 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt @@ -5,7 +5,9 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.Test +import org.ksmt.solver.KSolverStatus import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue +import org.ksmt.solver.z3.KZ3Solver import org.ksmt.utils.mkConst import kotlin.test.assertSame @@ -118,8 +120,7 @@ class TranslationTest { assertSame(expected, translated) } -// @Test - @RepeatedTest(30) + @RepeatedTest(10) fun testTranslateArrayCopy() = with(ctx) { var region = emptyInputArrayRegion(valueArrayDescr, bv32Sort) { (ref, idx), reg -> mkInputArrayReading(reg, ref, idx) @@ -156,6 +157,11 @@ class TranslationTest { val translated = translator.translate(reading) - assertSame(expected, translated) + // due to KSMT is non-deterministic with reorderings, we have to check it with solver + val solver = KZ3Solver(ctx) + solver.assert(expected neq translated) + val status = solver.check() + + assertSame(KSolverStatus.UNSAT, status) } } \ No newline at end of file From cf0897b854686826803736bdb7db3eabef00f345 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 10 Apr 2023 17:56:57 +0300 Subject: [PATCH 10/28] Rename: ModelDecoder.kt --- .../main/kotlin/org/usvm/{UModelDecoder.kt => ModelDecoder.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename usvm-core/src/main/kotlin/org/usvm/{UModelDecoder.kt => ModelDecoder.kt} (100%) diff --git a/usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt similarity index 100% rename from usvm-core/src/main/kotlin/org/usvm/UModelDecoder.kt rename to usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt From 9bab39cc83443774c9babe85c3160bedf6f8848d Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 11 Apr 2023 10:04:31 +0300 Subject: [PATCH 11/28] Refactor: builder functions for memory regions, non-functional changes in signatures and tests --- .../main/kotlin/org/usvm/ExprTranslator.kt | 2 + usvm-core/src/main/kotlin/org/usvm/Heap.kt | 16 ++---- .../src/main/kotlin/org/usvm/MemoryRegions.kt | 26 +++++----- .../src/main/kotlin/org/usvm/ModelDecoder.kt | 8 +-- usvm-core/src/main/kotlin/org/usvm/Solver.kt | 8 +-- .../test/kotlin/org/usvm/CompositionTest.kt | 33 ++++++++++-- .../src/test/kotlin/org/usvm/HeapRefEqTest.kt | 2 +- .../kotlin/org/usvm/HeapRefSplittingTest.kt | 6 +-- .../test/kotlin/org/usvm/ModelDecodingTest.kt | 50 ++++++++----------- .../src/test/kotlin/org/usvm/TestUtil.kt | 2 +- .../test/kotlin/org/usvm/TranslationTest.kt | 35 ++++++------- .../kotlin/org/usvm/UContextInterningTest.kt | 12 ++--- 12 files changed, 100 insertions(+), 100 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index f9b750ae37..195b6efcad 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -15,6 +15,7 @@ open class UExprTranslator constructor( error("You must override `transform` function in UExprTranslator for ${expr::class}") override fun transform(expr: URegisterReading): UExpr { + // TODO: we must ensure all ids are different val registerConst = expr.sort.mkConst("r${expr.idx}") return registerConst } @@ -26,6 +27,7 @@ open class UExprTranslator constructor( error("You must override `transform` function in UExprTranslator for ${expr::class}") override fun transform(expr: UIndexedMethodReturnValue): UExpr { + // TODO: we must ensure all ids are different val const = expr.sort.mkConst("m${expr.method}_${expr.callIndex}") return const } diff --git a/usvm-core/src/main/kotlin/org/usvm/Heap.kt b/usvm-core/src/main/kotlin/org/usvm/Heap.kt index ed047632d2..f7df31c941 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Heap.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Heap.kt @@ -110,9 +110,7 @@ data class URegionHeap( sort: Sort, ): UInputFieldRegion = inputFields[field].inputFieldsRegionUncheckedCast() - ?: emptyInputFieldRegion(field, sort) { key, region -> - ctx.mkInputFieldReading(region, key) - } + ?: emptyInputFieldRegion(field, sort) private fun allocatedArrayRegion( arrayType: ArrayType, @@ -120,26 +118,20 @@ data class URegionHeap( elementSort: Sort, ): UAllocatedArrayRegion = allocatedArrays[address].allocatedArrayRegionUncheckedCast() - ?: emptyAllocatedArrayRegion(arrayType, address, elementSort) { index, region -> - ctx.mkAllocatedArrayReading(region, index) - } + ?: emptyAllocatedArrayRegion(arrayType, address, elementSort) private fun inputArrayRegion( arrayType: ArrayType, elementSort: Sort, ): UInputArrayRegion = inputArrays[arrayType].inputArrayRegionUncheckedCast() - ?: emptyInputArrayRegion(arrayType, elementSort) { pair, region -> - ctx.mkInputArrayReading(region, pair.first, pair.second) - } + ?: emptyInputArrayRegion(arrayType, elementSort) private fun inputArrayLengthRegion( arrayType: ArrayType, ): UInputArrayLengthRegion = inputLengths[arrayType] - ?: emptyArrayLengthRegion(arrayType, ctx.sizeSort) { ref, region -> - ctx.mkInputArrayLengthReading(region, ref) - } + ?: emptyArrayLengthRegion(arrayType, ctx.sizeSort) override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr = ref.map( diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt index a0779ba850..89a7661a06 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt @@ -30,6 +30,7 @@ data class UMemoryRegion, Key, Sort : USort> ) { // to save memory usage val sort: Sort get() = regionId.sort + // if we replace it with get(), we have to check it every time in read function val defaultValue = regionId.defaultValue @@ -330,48 +331,47 @@ val UInputArrayLengthRegion.inputLengthArrayType fun emptyInputFieldRegion( field: Field, sort: Sort, - instantiator: UInstantiator, UHeapRef, Sort>, -): UInputFieldRegion = UMemoryRegion( - UInputFieldId(field, sort), - UFlatUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic), - instantiator -) +): UInputFieldRegion = + UMemoryRegion( + UInputFieldId(field, sort), + UFlatUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic) + ) { key, region -> sort.uctx.mkInputFieldReading(region, key) } fun emptyAllocatedArrayRegion( arrayType: ArrayType, address: UConcreteHeapAddress, sort: Sort, - instantiator: UInstantiator, USizeExpr, Sort>, ): UAllocatedArrayRegion { val updates = UTreeUpdates( updates = emptyRegionTree(), ::indexRegion, ::indexRangeRegion, ::indexEq, ::indexLeConcrete, ::indexLeSymbolic ) val regionId = UAllocatedArrayId(arrayType, address, sort, sort.sampleValue()) - return UMemoryRegion(regionId, updates, instantiator) + return UMemoryRegion(regionId, updates) { key, region -> + sort.uctx.mkAllocatedArrayReading(region, key) + } } fun emptyInputArrayRegion( arrayType: ArrayType, sort: Sort, - instantiator: UInstantiator, USymbolicArrayIndex, Sort>, ): UInputArrayRegion { val updates = UTreeUpdates( updates = emptyRegionTree(), ::refIndexRegion, ::refIndexRangeRegion, ::refIndexEq, ::refIndexCmpConcrete, ::refIndexCmpSymbolic ) - return UMemoryRegion(UInputArrayId(arrayType, sort), updates, instantiator) + return UMemoryRegion(UInputArrayId(arrayType, sort), updates) { pair, region -> + sort.uctx.mkInputArrayReading(region, pair.first, pair.second) + } } fun emptyArrayLengthRegion( arrayType: ArrayType, sizeSort: USizeSort, - instantiator: UInstantiator, UHeapRef, USizeSort>, ): UInputArrayLengthRegion = UMemoryRegion( UInputArrayLengthId(arrayType, sizeSort), UFlatUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic), - instantiator - ) + ) { ref, region -> sizeSort.uctx.mkInputArrayLengthReading(region, ref) } //endregion diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt index 6e1457e1a5..58b9b48703 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt @@ -129,7 +129,7 @@ class URegistersStackModel(private val registers: Map>) : } class UHeapModel( - private val nullRef: UExpr, + private val nullRef: UConcreteHeapRef, private val regionEvaluatorProvider: URegionEvaluatorProvider, private var resolvedInputFields: PersistentMap>, private var resolvedInputArrays: PersistentMap, out USort>>, @@ -295,7 +295,7 @@ class UHeapModel( override fun allocateArray(count: USizeExpr): UConcreteHeapAddress = error("Illegal operation for a model") - override fun clone(): UHeap, USizeExpr, Field, ArrayType, UBoolExpr> = + override fun clone(): UHeapModel = UHeapModel( nullRef, regionEvaluatorProvider, @@ -304,9 +304,9 @@ class UHeapModel( resolvedInputLengths.mapValues { evaluator -> evaluator.clone() }, ) - override fun nullRef(): UHeapRef = nullRef + override fun nullRef(): UConcreteHeapRef = nullRef - override fun toMutableHeap() = clone() + override fun toMutableHeap(): UHeapModel = clone() } inline private fun PersistentMap.mapValues(crossinline mapper: (V) -> V): PersistentMap = diff --git a/usvm-core/src/main/kotlin/org/usvm/Solver.kt b/usvm-core/src/main/kotlin/org/usvm/Solver.kt index e6573a3e93..2e074993de 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Solver.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Solver.kt @@ -1,13 +1,7 @@ -package org.usvm.concrete.interpreter +package org.usvm import org.ksmt.solver.KSolver import org.ksmt.solver.KSolverStatus -import org.usvm.UContext -import org.usvm.UExprTranslator -import org.usvm.UMemoryBase -import org.usvm.UModelBase -import org.usvm.UModelDecoder -import org.usvm.UPathCondition sealed interface USolverResult diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index 296a91f05c..a1f14c9412 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -13,6 +13,7 @@ import org.ksmt.expr.printer.ExpressionPrinter import org.ksmt.expr.transformer.KTransformerBase import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.sort.KBv32Sort +import org.usvm.UAddressCounter.Companion.NULL_ADDRESS import kotlin.reflect.KClass import kotlin.test.assertEquals import kotlin.test.assertSame @@ -296,9 +297,7 @@ internal class CompositionTest { val arrayType: KClass> = Array::class // Create an empty region - val region = emptyInputArrayRegion(arrayType, mkBv32Sort()) { key, memoryRegion -> - mkInputArrayReading(memoryRegion, key.first, key.second) - } + val region = emptyInputArrayRegion(arrayType, mkBv32Sort()) // TODO replace with jacoDB type // create a reading from the region @@ -448,4 +447,32 @@ internal class CompositionTest { assert(composedExpression === answer) } + + @Test + fun testHeapRefEq() = with(ctx) { + val stackModel = URegistersStackModel(mapOf(0 to mkConcreteHeapRef(-1), 1 to mkConcreteHeapRef(-2))) + + val composer = UComposer(this, stackModel, mockk(), mockk(), mockk()) + + val heapRefEvalEq = mkHeapRefEq(mkRegisterReading(0, addressSort), mkRegisterReading(1, addressSort)) + + val expr = composer.compose(heapRefEvalEq) + assertSame(falseExpr, expr) + } + + @Test + fun testHeapRefNullAddress() = with(ctx) { + val stackModel = URegistersStackModel(mapOf(0 to mkConcreteHeapRef(0))) + + val heapEvaluator: UReadOnlySymbolicHeap = mockk() + every { heapEvaluator.nullRef() } returns mkConcreteHeapRef(NULL_ADDRESS) + + val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + + val heapRefEvalEq = mkHeapRefEq(mkRegisterReading(0, addressSort), nullRef) + + val expr = composer.compose(heapRefEvalEq) + assertSame(trueExpr, expr) + } + } diff --git a/usvm-core/src/test/kotlin/org/usvm/HeapRefEqTest.kt b/usvm-core/src/test/kotlin/org/usvm/HeapRefEqTest.kt index f1b1d08e72..cb8db1eb3f 100644 --- a/usvm-core/src/test/kotlin/org/usvm/HeapRefEqTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/HeapRefEqTest.kt @@ -7,7 +7,7 @@ import kotlin.test.assertSame class HeapRefEqTest { private lateinit var ctx: UContext - private lateinit var heap: URegionHeap + private lateinit var heap: URegionHeap @BeforeEach fun initializeContext() { diff --git a/usvm-core/src/test/kotlin/org/usvm/HeapRefSplittingTest.kt b/usvm-core/src/test/kotlin/org/usvm/HeapRefSplittingTest.kt index 07fd038f94..316d5f1bfb 100644 --- a/usvm-core/src/test/kotlin/org/usvm/HeapRefSplittingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/HeapRefSplittingTest.kt @@ -12,11 +12,11 @@ import kotlin.test.assertSame class HeapRefSplittingTest { private lateinit var ctx: UContext - private lateinit var heap: URegionHeap + private lateinit var heap: URegionHeap private lateinit var valueFieldDescr: Pair private lateinit var addressFieldDescr: Pair - private lateinit var arrayDescr: Pair + private lateinit var arrayDescr: Pair @BeforeEach fun initializeContext() { @@ -24,7 +24,7 @@ class HeapRefSplittingTest { heap = URegionHeap(ctx) valueFieldDescr = mockk() to ctx.bv32Sort addressFieldDescr = mockk() to ctx.addressSort - arrayDescr = mockk() to ctx.addressSort + arrayDescr = mockk() to ctx.addressSort } @Test diff --git a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt index 3f782d6bcc..351933690b 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt @@ -1,10 +1,8 @@ package org.usvm -import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.ksmt.utils.mkConst import org.usvm.UAddressCounter.Companion.NULL_ADDRESS import kotlin.test.assertSame import kotlinx.collections.immutable.persistentMapOf @@ -20,60 +18,52 @@ class ModelDecodingTest { @Test fun testVeryTrickyCase() = with(ctx) { val heapModel = - UHeapModel(mkConcreteHeapRef(0), mockk(), persistentMapOf(), persistentMapOf(), persistentMapOf()) + UHeapModel( + mkConcreteHeapRef(NULL_ADDRESS), + mockk(), + persistentMapOf(), + persistentMapOf(), + persistentMapOf() + ) val stackModel = URegistersStackModel(mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) val model = UModelBase(this, stackModel, heapModel, mockk(), mockk()) - val region = emptyAllocatedArrayRegion(mockk(), 1, bv32Sort) { key, reg -> - ctx.mkAllocatedArrayReading(reg, key) - } + val region = emptyAllocatedArrayRegion(mockk(), 1, bv32Sort) .write(0.toBv(), 0.toBv(), trueExpr) .write(1.toBv(), 1.toBv(), trueExpr) .write(mkRegisterReading(1, sizeSort), 2.toBv(), trueExpr) .write(mkRegisterReading(2, sizeSort), 3.toBv(), trueExpr) val reading = region.read(mkRegisterReading(0, sizeSort)) - val expr = model.eval(reading) + val expr = model.eval(reading) assertSame(mkBv(2), expr) } @Test fun testTrickyCase() = with(ctx) { + val concreteNull = mkConcreteHeapRef(NULL_ADDRESS) + val heapModel = - UHeapModel(mkConcreteHeapRef(0), mockk(), persistentMapOf(), persistentMapOf(), persistentMapOf()) + UHeapModel( + concreteNull, + mockk(), + persistentMapOf(), + persistentMapOf(), + persistentMapOf() + ) val stackModel = URegistersStackModel(mapOf(0 to mkBv(0), 1 to mkBv(0), 2 to mkBv(2))) val model = UModelBase(this, stackModel, heapModel, mockk(), mockk()) - val region = emptyAllocatedArrayRegion(mockk(), 1, addressSort) { key, reg -> - mkAllocatedArrayReading(reg, key) - } + val region = emptyAllocatedArrayRegion(mockk(), 1, addressSort) val reading = region.read(mkRegisterReading(0, sizeSort)) val expr = model.eval(reading) - assertSame(mkConcreteHeapRef(NULL_ADDRESS), expr) - } - - @Test - @Suppress("UNUSED_VARIABLE") - fun testHeapRefEq() = with(ctx) { - val translator = UExprTranslator(this) - - val heapModel = - UHeapModel(mkConcreteHeapRef(0), mockk(), persistentMapOf(), persistentMapOf(), persistentMapOf()) - - val stackModel = URegistersStackModel(mapOf(0 to mkConcreteHeapRef(-1), 1 to mkConcreteHeapRef(-2))) - - val model = UModelBase(this, stackModel, heapModel, mockk(), mockk()) - - val heapRefEvalEq = mkHeapRefEq(mkRegisterReading(0, addressSort), mkRegisterReading(1, addressSort)) - - val expr = model.eval(heapRefEvalEq) - assertSame(falseExpr, expr) + assertSame(concreteNull, expr) } } diff --git a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt index a1a2c7ea90..b0bfac17fb 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt @@ -1,5 +1,5 @@ package org.usvm typealias Field = java.lang.reflect.Field -typealias ArrayType = kotlin.reflect.KClass<*> +typealias Type = kotlin.reflect.KClass<*> typealias Method = kotlin.reflect.KFunction<*> diff --git a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt index e08483bf51..761949aee3 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt @@ -13,13 +13,13 @@ import kotlin.test.assertSame class TranslationTest { private lateinit var ctx: UContext - private lateinit var heap: URegionHeap - private lateinit var translator: UExprTranslator + private lateinit var heap: URegionHeap + private lateinit var translator: UExprTranslator private lateinit var valueFieldDescr: Pair private lateinit var addressFieldDescr: Pair - private lateinit var valueArrayDescr: ArrayType - private lateinit var addressArrayDescr: ArrayType + private lateinit var valueArrayDescr: Type + private lateinit var addressArrayDescr: Type @BeforeEach fun initializeContext() { @@ -87,11 +87,7 @@ class TranslationTest { } @Test - fun testTranslate2dArray() = with(ctx) { - var region = emptyInputArrayRegion(valueArrayDescr, bv32Sort) { (ref, idx), reg -> - mkInputArrayReading(reg, ref, idx) - } - + fun testTranslate2DArray() = with(ctx) { val ref1 = mkRegisterReading(0, addressSort) val idx1 = mkRegisterReading(1, sizeSort) val val1 = mkBv(1) @@ -101,8 +97,9 @@ class TranslationTest { val val2 = mkBv(2) - region = region.write(ref1 to idx1, val1, trueExpr) - region = region.write(ref2 to idx2, val2, trueExpr) + val region = emptyInputArrayRegion(valueArrayDescr, bv32Sort) + .write(ref1 to idx1, val1, trueExpr) + .write(ref2 to idx2, val2, trueExpr) val ref3 = mkRegisterReading(4, addressSort) val idx3 = mkRegisterReading(5, sizeSort) @@ -122,9 +119,7 @@ class TranslationTest { @RepeatedTest(10) fun testTranslateArrayCopy() = with(ctx) { - var region = emptyInputArrayRegion(valueArrayDescr, bv32Sort) { (ref, idx), reg -> - mkInputArrayReading(reg, ref, idx) - } + var region = emptyInputArrayRegion(valueArrayDescr, bv32Sort) val ref1 = mkRegisterReading(0, addressSort) val idx1 = mkRegisterReading(1, sizeSort) @@ -139,9 +134,8 @@ class TranslationTest { val concreteRef = mkConcreteHeapRef(heap.allocate()) - var concreteRegion = emptyAllocatedArrayRegion(valueArrayDescr, concreteRef.address, bv32Sort) { idx, reg -> - mkAllocatedArrayReading(reg, idx) - } + var concreteRegion = emptyAllocatedArrayRegion(valueArrayDescr, concreteRef.address, bv32Sort) + val keyConverter = UInputToAllocatedKeyConverter(ref1 to mkBv(0), concreteRef to mkBv(0), mkBv(5)) concreteRegion = concreteRegion.copyRange(region, mkBv(0), mkBv(5), keyConverter, trueExpr) @@ -152,13 +146,14 @@ class TranslationTest { val key = region.regionId.keyMapper(translator)(keyConverter.convert(translator.translate(idx))) val innerReading = translator.translateRegionReading(region, key) - val guard = translator.translate((mkBvSignedLessOrEqualExpr(mkBv(0), idx)) and mkBvSignedLessOrEqualExpr(idx, mkBv(5))) + val guard = + translator.translate((mkBvSignedLessOrEqualExpr(mkBv(0), idx)) and mkBvSignedLessOrEqualExpr(idx, mkBv(5))) val expected = mkIte(guard, innerReading, bv32Sort.sampleValue()) val translated = translator.translate(reading) - // due to KSMT is non-deterministic with reorderings, we have to check it with solver - val solver = KZ3Solver(ctx) + // due to KSMT non-deterministic with reorderings, we have to check it with solver + val solver = KZ3Solver(this) solver.assert(expected neq translated) val status = solver.check() diff --git a/usvm-core/src/test/kotlin/org/usvm/UContextInterningTest.kt b/usvm-core/src/test/kotlin/org/usvm/UContextInterningTest.kt index 4725858c22..1e6eece8f6 100644 --- a/usvm-core/src/test/kotlin/org/usvm/UContextInterningTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/UContextInterningTest.kt @@ -79,8 +79,8 @@ class UContextInterningTest { @Test fun testAllocatedArrayReadingInterning() = with(context) { - val fstRegion = mockk>() - val sndRegion = mockk>() + val fstRegion = mockk>() + val sndRegion = mockk>() every { fstRegion.sort } returns bv32Sort every { sndRegion.sort } returns boolSort @@ -105,8 +105,8 @@ class UContextInterningTest { @Test fun testInputArrayReadingInterning() = with(context) { - val fstRegion = mockk>() - val sndRegion = mockk>() + val fstRegion = mockk>() + val sndRegion = mockk>() every { fstRegion.sort } returns bv32Sort every { sndRegion.sort } returns boolSort @@ -136,8 +136,8 @@ class UContextInterningTest { @Test fun testArrayLengthInterning() = with(context) { - val fstRegion = mockk>() - val sndRegion = mockk>() + val fstRegion = mockk>() + val sndRegion = mockk>() every { fstRegion.sort } returns sizeSort every { sndRegion.sort } returns sizeSort From 0de3f6f9612bb3c11bb86c4cc0e2d16522e6c355 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 11 Apr 2023 10:16:56 +0300 Subject: [PATCH 12/28] Move: tests from ModelDecodingTest.kt to CompositionTest.kt --- .../test/kotlin/org/usvm/CompositionTest.kt | 55 ++++++++++++++++++- .../test/kotlin/org/usvm/ModelDecodingTest.kt | 51 ----------------- 2 files changed, 54 insertions(+), 52 deletions(-) diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index a1f14c9412..bd69c77ae1 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -17,8 +17,9 @@ import org.usvm.UAddressCounter.Companion.NULL_ADDRESS import kotlin.reflect.KClass import kotlin.test.assertEquals import kotlin.test.assertSame +import kotlinx.collections.immutable.persistentMapOf -internal class CompositionTest { +internal class CompositionTest { private lateinit var stackEvaluator: URegistersStackEvaluator private lateinit var heapEvaluator: UReadOnlySymbolicHeap private lateinit var typeEvaluator: UTypeEvaluator @@ -475,4 +476,56 @@ internal class CompositionTest { assertSame(trueExpr, expr) } + @Test + fun testComposingAllocatedArray() = with(ctx) { + val heapModel = + UHeapModel( + mkConcreteHeapRef(NULL_ADDRESS), + mockk(), + persistentMapOf(), + persistentMapOf(), + persistentMapOf() + ) + + + val stackModel = URegistersStackModel(mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) + + val model = UModelBase(this, stackModel, heapModel, mockk(), mockk()) + + + val region = emptyAllocatedArrayRegion(mockk(), 1, bv32Sort) + .write(0.toBv(), 0.toBv(), trueExpr) + .write(1.toBv(), 1.toBv(), trueExpr) + .write(mkRegisterReading(1, sizeSort), 2.toBv(), trueExpr) + .write(mkRegisterReading(2, sizeSort), 3.toBv(), trueExpr) + val reading = region.read(mkRegisterReading(0, sizeSort)) + + val expr = model.eval(reading) + assertSame(mkBv(2), expr) + } + + @Test + fun testNullRefRegionDefaultValue() = with(ctx) { + val concreteNull = mkConcreteHeapRef(NULL_ADDRESS) + + val heapModel = + UHeapModel( + concreteNull, + mockk(), + persistentMapOf(), + persistentMapOf(), + persistentMapOf() + ) + + val stackModel = URegistersStackModel(mapOf(0 to mkBv(0), 1 to mkBv(0), 2 to mkBv(2))) + + val model = UModelBase(this, stackModel, heapModel, mockk(), mockk()) + + + val region = emptyAllocatedArrayRegion(mockk(), 1, addressSort) + val reading = region.read(mkRegisterReading(0, sizeSort)) + + val expr = model.eval(reading) + assertSame(concreteNull, expr) + } } diff --git a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt index 351933690b..b33dcb3157 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt @@ -15,55 +15,4 @@ class ModelDecodingTest { ctx = UContext() } - @Test - fun testVeryTrickyCase() = with(ctx) { - val heapModel = - UHeapModel( - mkConcreteHeapRef(NULL_ADDRESS), - mockk(), - persistentMapOf(), - persistentMapOf(), - persistentMapOf() - ) - - val stackModel = URegistersStackModel(mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) - - val model = UModelBase(this, stackModel, heapModel, mockk(), mockk()) - - - val region = emptyAllocatedArrayRegion(mockk(), 1, bv32Sort) - .write(0.toBv(), 0.toBv(), trueExpr) - .write(1.toBv(), 1.toBv(), trueExpr) - .write(mkRegisterReading(1, sizeSort), 2.toBv(), trueExpr) - .write(mkRegisterReading(2, sizeSort), 3.toBv(), trueExpr) - val reading = region.read(mkRegisterReading(0, sizeSort)) - - val expr = model.eval(reading) - assertSame(mkBv(2), expr) - } - - @Test - fun testTrickyCase() = with(ctx) { - val concreteNull = mkConcreteHeapRef(NULL_ADDRESS) - - val heapModel = - UHeapModel( - concreteNull, - mockk(), - persistentMapOf(), - persistentMapOf(), - persistentMapOf() - ) - - val stackModel = URegistersStackModel(mapOf(0 to mkBv(0), 1 to mkBv(0), 2 to mkBv(2))) - - val model = UModelBase(this, stackModel, heapModel, mockk(), mockk()) - - - val region = emptyAllocatedArrayRegion(mockk(), 1, addressSort) - val reading = region.read(mkRegisterReading(0, sizeSort)) - - val expr = model.eval(reading) - assertSame(concreteNull, expr) - } } From d68bbef24ca1d6ee67c05a9b18c258e769d07248 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 11 Apr 2023 12:22:58 +0300 Subject: [PATCH 13/28] Add: more translating tests; Fix: found a bug in KSMT; --- buildSrc/src/main/kotlin/Versions.kt | 2 +- usvm-core/src/main/kotlin/org/usvm/Context.kt | 3 +- .../test/kotlin/org/usvm/TranslationTest.kt | 107 ++++++++++++++++-- 3 files changed, 101 insertions(+), 11 deletions(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index c114331326..68f0825f99 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -1,5 +1,5 @@ object Versions { - const val ksmt = "0.4.6" + const val ksmt = "c9a39a7843" const val collections = "0.3.5" const val coroutines = "1.6.4" const val jcdb = "a5c479bcf8" diff --git a/usvm-core/src/main/kotlin/org/usvm/Context.kt b/usvm-core/src/main/kotlin/org/usvm/Context.kt index c0089a7720..4aa64da7df 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Context.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Context.kt @@ -2,6 +2,7 @@ package org.usvm import org.ksmt.KAst import org.ksmt.KContext +import org.ksmt.expr.KConst import org.ksmt.expr.KExpr import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.sort.KBoolSort @@ -48,7 +49,7 @@ open class UContext( * @return the new equal rewritten expression without [UConcreteHeapRef]s */ override fun mkEq(lhs: KExpr, rhs: KExpr, order: Boolean): KExpr = - if (lhs.sort == addressSort) { + if (lhs.sort == addressSort && lhs !is KConst<*> && rhs !is KConst<*>) { mkHeapRefEq(lhs.asExpr(addressSort), rhs.asExpr(addressSort)) } else { super.mkEq(lhs, rhs, order) diff --git a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt index 761949aee3..190b247aad 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt @@ -2,7 +2,6 @@ package org.usvm import io.mockk.mockk import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.Test import org.ksmt.solver.KSolverStatus @@ -34,7 +33,6 @@ class TranslationTest { } @Test - @Disabled("TODO: rebase on ref splitting PR") fun testTranslateConstAddressSort() = with(ctx) { val ref = mkConcreteHeapRef(heap.allocate()) val idx = mkRegisterReading(0, sizeSort) @@ -42,7 +40,7 @@ class TranslationTest { val expr = heap.readArrayIndex(ref, idx, addressArrayDescr, addressSort) val translated = translator.translate(expr) - assertSame(nullRef, translated) + assertSame(translator.translate(nullRef), translated) } @Test @@ -118,8 +116,7 @@ class TranslationTest { } @RepeatedTest(10) - fun testTranslateArrayCopy() = with(ctx) { - var region = emptyInputArrayRegion(valueArrayDescr, bv32Sort) + fun testTranslateInputToAllocatedArrayCopy() = with(ctx) { val ref1 = mkRegisterReading(0, addressSort) val idx1 = mkRegisterReading(1, sizeSort) @@ -129,15 +126,16 @@ class TranslationTest { val idx2 = mkRegisterReading(3, sizeSort) val val2 = mkBv(2) - region = region.write(ref1 to idx1, val1, trueExpr) - region = region.write(ref2 to idx2, val2, trueExpr) + val region = emptyInputArrayRegion(valueArrayDescr, bv32Sort) + .write(ref1 to idx1, val1, trueExpr) + .write(ref2 to idx2, val2, trueExpr) val concreteRef = mkConcreteHeapRef(heap.allocate()) - var concreteRegion = emptyAllocatedArrayRegion(valueArrayDescr, concreteRef.address, bv32Sort) val keyConverter = UInputToAllocatedKeyConverter(ref1 to mkBv(0), concreteRef to mkBv(0), mkBv(5)) - concreteRegion = concreteRegion.copyRange(region, mkBv(0), mkBv(5), keyConverter, trueExpr) + val concreteRegion = emptyAllocatedArrayRegion(valueArrayDescr, concreteRef.address, bv32Sort) + .copyRange(region, mkBv(0), mkBv(5), keyConverter, trueExpr) val idx = mkRegisterReading(4, sizeSort) val reading = concreteRegion.read(idx) @@ -159,4 +157,95 @@ class TranslationTest { assertSame(KSolverStatus.UNSAT, status) } + + @Test + fun testTranslateInputFieldArray() = with(ctx) { + val ref1 = mkRegisterReading(1, addressSort) + val ref2 = mkRegisterReading(2, addressSort) + val ref3 = mkRegisterReading(3, addressSort) + + val g1 = mkRegisterReading(-1, boolSort) + val g2 = mkRegisterReading(-2, boolSort) + val g3 = mkRegisterReading(-3, boolSort) + + val region = emptyInputFieldRegion(mockk(), bv32Sort) + .write(ref1, mkBv(1), g1) + .write(ref2, mkBv(2), g2) + .write(ref3, mkBv(3), g3) + + val ref0 = mkRegisterReading(0, addressSort) + val reading = region.read(ref0) + + val ref0Eq1Or2Or3 = (ref0 eq ref1) or (ref0 eq ref2) or (ref0 eq ref3) + val readingNeq123 = (reading neq mkBv(1)) and (reading neq mkBv(2)) and (reading neq mkBv(3)) + val expr = ref0Eq1Or2Or3 and readingNeq123 + + val translated = translator.translate(expr and g1 and g2 and g3) + + val solver = KZ3Solver(this) + solver.assert(translated) + assertSame(KSolverStatus.UNSAT, solver.check()) + } + + @Test + fun testTranslateInputArrayLengthArray() = with(ctx) { + val ref1 = mkRegisterReading(1, addressSort) + val ref2 = mkRegisterReading(2, addressSort) + val ref3 = mkRegisterReading(3, addressSort) + + val region = emptyArrayLengthRegion(mockk(), bv32Sort) + .write(ref1, mkBv(1), trueExpr) + .write(ref2, mkBv(2), trueExpr) + .write(ref3, mkBv(3), trueExpr) + + val ref0 = mkRegisterReading(0, addressSort) + val reading = region.read(ref0) + + val ref0Eq1Or2Or3 = (ref0 eq ref1) or (ref0 eq ref2) or (ref0 eq ref3) + val readingNeq123 = (reading neq mkBv(1)) and (reading neq mkBv(2)) and (reading neq mkBv(3)) + val expr = ref0Eq1Or2Or3 and readingNeq123 + + val translated = translator.translate(expr) + + val solver = KZ3Solver(this) + solver.assert(translated) + assertSame(KSolverStatus.UNSAT, solver.check()) + } + + @Test + fun testTranslateInputToInputArrayCopy() = with(ctx) { + + val ref1 = mkRegisterReading(0, addressSort) + val idx1 = mkRegisterReading(1, sizeSort) + val val1 = mkBv(1) + + val ref2 = mkRegisterReading(2, addressSort) + val idx2 = mkRegisterReading(3, sizeSort) + val val2 = mkBv(2) + + val inputRegion1 = emptyInputArrayRegion(valueArrayDescr, bv32Sort) + .write(ref1 to idx1, val1, trueExpr) + .write(ref2 to idx2, val2, trueExpr) + + + val keyConverter = UInputToInputKeyConverter(ref1 to mkBv(0), ref1 to mkBv(0), mkBv(5)) + var inputRegion2 = emptyInputArrayRegion(mockk(), bv32Sort) + + val idx = mkRegisterReading(4, sizeSort) + val reading1 = inputRegion2.read(ref2 to idx) + + inputRegion2 = inputRegion2 + .copyRange(inputRegion1, ref1 to mkBv(0), ref1 to mkBv(5), keyConverter, trueExpr) + + val reading2 = inputRegion2.read(ref2 to idx) + + val expr = (reading1 neq reading2) and (ref1 neq ref2) + val translated = translator.translate(expr) + + val solver = KZ3Solver(this) + solver.assert(translated) + val status = solver.check() + + assertSame(KSolverStatus.UNSAT, status) + } } \ No newline at end of file From 77598bd3439383718c84c718fbfcfdf0c587541b Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 11 Apr 2023 13:37:59 +0300 Subject: [PATCH 14/28] Refactor: UMemoryUpdatesVisitor --- .../main/kotlin/org/usvm/ExprTranslator.kt | 20 ++- .../src/main/kotlin/org/usvm/MemoryUpdates.kt | 96 ++++++++++++- .../main/kotlin/org/usvm/RegionTranslator.kt | 136 +++--------------- 3 files changed, 124 insertions(+), 128 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index 195b6efcad..2edb602cb9 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -77,36 +77,32 @@ open class UExprTranslator constructor( regionId: UInputFieldId, ): URegionTranslator, UHeapRef, Sort, *> { val initialValue = regionIdInitialValueProvider.visit(regionId) - val updateTranslator = U1DArrayUpdateTranslator(this, initialValue) - val updatesTranslator = UFlatUpdatesTranslator(updateTranslator) - return URegionTranslator(updateTranslator, updatesTranslator) + val updateTranslator = U1DArrayUpdateTranslate(this, initialValue) + return URegionTranslator(updateTranslator) } override fun visit( regionId: UAllocatedArrayId, ): URegionTranslator, USizeExpr, Sort, *> { val initialValue = regionIdInitialValueProvider.visit(regionId) - val updateTranslator = U1DArrayUpdateTranslator(this, initialValue) - val updatesTranslator = UTreeUpdatesTranslator(updateTranslator) - return URegionTranslator(updateTranslator, updatesTranslator) + val updateTranslator = U1DArrayUpdateTranslate(this, initialValue) + return URegionTranslator(updateTranslator) } override fun visit( regionId: UInputArrayId, ): URegionTranslator, USymbolicArrayIndex, Sort, *> { val initialValue = regionIdInitialValueProvider.visit(regionId) - val updateTranslator = U2DArrayUpdateTranslator(this, initialValue) - val updatesTranslator = UTreeUpdatesTranslator(updateTranslator) - return URegionTranslator(updateTranslator, updatesTranslator) + val updateTranslator = U2DArrayUpdateVisitor(this, initialValue) + return URegionTranslator(updateTranslator) } override fun visit( regionId: UInputArrayLengthId, ): URegionTranslator, UHeapRef, USizeSort, *> { val initialValue = regionIdInitialValueProvider.visit(regionId) - val updateTranslator = U1DArrayUpdateTranslator(this, initialValue) - val updatesTranslator = UFlatUpdatesTranslator(updateTranslator) - return URegionTranslator(updateTranslator, updatesTranslator) + val updateTranslator = U1DArrayUpdateTranslate(this, initialValue) + return URegionTranslator(updateTranslator) } val regionIdInitialValueProvider = URegionIdInitialValueProviderBase(onDefaultValuePresent = { translate(it) }) diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt index 2125241729..4fd4ce5e86 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt @@ -3,6 +3,7 @@ package org.usvm import org.usvm.util.Region import org.usvm.util.RegionTree import org.usvm.util.emptyRegionTree +import java.util.IdentityHashMap /** * Represents a sequence of memory writes. @@ -69,6 +70,20 @@ interface UMemoryUpdates : Sequence> { * Returns true if there were any updates and false otherwise. */ fun isEmpty(): Boolean + + /** + * Accepts the [visitor]. Calls `visitInitialValue` firstly, then calls `visitUpdateNode` in the chronological order + * (from the oldest to the newest) with accumulated [Result]. + * + * Uses [lookupCache] to shortcut the traversal. The actual key is determined by the + * [UMemoryUpdates] implementation. It's caller's responsibility to maintain the lifetime of the [lookupCache]. + * + * @return the final result. + */ + fun accept( + visitor: UMemoryUpdatesVisitor, + lookupCache: MutableMap, + ): Result } @@ -189,6 +204,24 @@ class UFlatUpdates private constructor( override fun lastUpdatedElementOrNull(): UUpdateNode? = node?.update override fun isEmpty(): Boolean = node == null + override fun accept( + visitor: UMemoryUpdatesVisitor, + lookupCache: MutableMap, + ): Result = + UFlatMemoryUpdatesFolder(visitor, lookupCache).fold() + + private inner class UFlatMemoryUpdatesFolder( + private val visitor: UMemoryUpdatesVisitor, + private val cache: MutableMap, + ) { + fun fold() = foldFlatUpdates(this@UFlatUpdates) + private fun foldFlatUpdates(updates: UFlatUpdates): Result = + cache.getOrPut(updates) { + val node = updates.node ?: return@getOrPut visitor.visitInitialValue() + val accumulated = foldFlatUpdates(node.next) + visitor.visitUpdate(accumulated, node.update) + } + } } //endregion @@ -196,7 +229,7 @@ class UFlatUpdates private constructor( //region Tree memory updates data class UTreeUpdates, Sort : USort>( - internal val updates: RegionTree, Reg>, + private val updates: RegionTree, Reg>, private val keyToRegion: (Key) -> Reg, private val keyRangeToRegion: (Key, Key) -> Reg, private val symbolicEq: (Key, Key) -> UBoolExpr, @@ -428,6 +461,67 @@ data class UTreeUpdates, Sort : USort>( updates.entries.entries.lastOrNull()?.value?.first override fun isEmpty(): Boolean = updates.entries.isEmpty() + override fun accept( + visitor: UMemoryUpdatesVisitor, + lookupCache: MutableMap, + ): Result = + UTreeMemoryUpdatesFolder(visitor, lookupCache).fold() + + private inner class UTreeMemoryUpdatesFolder( + private val visitor: UMemoryUpdatesVisitor, + private val cache: MutableMap, + ) { + fun fold(): Result = + cache.getOrPut(updates) { + leftMostTranslate(updates) + } + + private val emittedUpdates = hashSetOf>() + + private fun leftMostTranslate(updates: RegionTree, *>): Result { + var result = cache[updates] + + if (result != null) { + return result + } + + val entryIterator = updates.entries.iterator() + if (!entryIterator.hasNext()) { + return visitor.visitInitialValue() + } + val (update, nextUpdates) = entryIterator.next().value + result = leftMostTranslate(nextUpdates) + result = visitor.visitUpdate(result, update) + return notLeftMostTranslate(result, entryIterator) + } + + private fun notLeftMostTranslate( + accumulator: Result, + iterator: Iterator, Pair, RegionTree, *>>>>, + ): Result { + var accumulated = accumulator + while (iterator.hasNext()) { + val (reg, entry) = iterator.next() + val (update, tree) = entry + accumulated = notLeftMostTranslate(accumulated, tree.entries.iterator()) + + accumulated = addIfNeeded(accumulated, update, reg) + } + return accumulated + } + + private fun addIfNeeded(accumulated: Result, update: UUpdateNode, region: Region<*>): Result { + if (checkWasCloned(update, region)) { + if (update in emittedUpdates) { + return accumulated + } + emittedUpdates += update + visitor.visitUpdate(accumulated, update) + } + + return visitor.visitUpdate(accumulated, update) + } + } } //endregion \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt index 3fa3cc5021..c3369c08e3 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt @@ -2,47 +2,43 @@ package org.usvm import org.ksmt.sort.KArray2Sort import org.ksmt.sort.KArraySort -import org.usvm.util.Region -import org.usvm.util.RegionTree import java.util.IdentityHashMap /** * [URegionTranslator] defines a template method that translates a region reading to a specific [UExpr] with a sort [Sort]. */ class URegionTranslator, Key, Sort : USort, Result>( - private val updateTranslator: UUpdateTranslator, - private val updatesTranslator: UUpdatesTranslator, + private val updateTranslator: UMemoryUpdatesVisitor, ) { fun translateReading(region: UMemoryRegion, key: Key): UExpr { val translated = translate(region) - return updateTranslator.select(translated, key) + return updateTranslator.visitSelect(translated, key) } - private val cache = IdentityHashMap, Result>() + private val visitorCache = IdentityHashMap() private fun translate(region: UMemoryRegion): Result = - cache.getOrPut(region) { updatesTranslator.translateUpdates(region.updates) } + region.updates.accept(updateTranslator, visitorCache) } -interface UUpdateTranslator { - fun select(result: Result, key: Key): UExpr +interface UMemoryUpdatesVisitor { + fun visitSelect(result: Result, key: Key): UExpr - fun initialValue(): Result + fun visitInitialValue(): Result - fun applyUpdate(previous: Result, update: UUpdateNode): Result + fun visitUpdate(previous: Result, update: UUpdateNode): Result } -internal class U1DArrayUpdateTranslator( +internal class U1DArrayUpdateTranslate( private val exprTranslator: UExprTranslator<*, *>, - private val initialValue: UExpr> - -) : UUpdateTranslator, Sort, UExpr>> { - override fun select(result: UExpr>, key: UExpr): UExpr = + private val initialValue: UExpr>, +) : UMemoryUpdatesVisitor, Sort, UExpr>> { + override fun visitSelect(result: UExpr>, key: UExpr): UExpr = result.ctx.mkArraySelect(result, key) - override fun initialValue(): UExpr> = initialValue + override fun visitInitialValue(): UExpr> = initialValue - override fun applyUpdate( + override fun visitUpdate( previous: UExpr>, update: UUpdateNode, Sort>, ): UExpr> = with(previous.uctx) { @@ -79,25 +75,26 @@ internal class U1DArrayUpdateTranslator( private val UExpr.translated get() = exprTranslator.translate(this) } -internal class U2DArrayUpdateTranslator< +internal class U2DArrayUpdateVisitor< Key1Sort : USort, Key2Sort : USort, - Sort : USort>( + Sort : USort, + >( private val exprTranslator: UExprTranslator<*, *>, private val initialValue: UExpr>, -) : UUpdateTranslator, UExpr>, Sort, UExpr>> { +) : UMemoryUpdatesVisitor, UExpr>, Sort, UExpr>> { /** * [key] is already translated, so we don't have to call it explicitly. */ - override fun select( + override fun visitSelect( result: UExpr>, key: Pair, UExpr>, ): UExpr = result.ctx.mkArraySelect(result, key.first, key.second) - override fun initialValue(): UExpr> = initialValue + override fun visitInitialValue(): UExpr> = initialValue - override fun applyUpdate( + override fun visitUpdate( previous: UExpr>, update: UUpdateNode, UExpr>, Sort>, ): UExpr> = with(previous.uctx) { @@ -134,94 +131,3 @@ internal class U2DArrayUpdateTranslator< private val UExpr.translated get() = exprTranslator.translate(this) } - -interface UUpdatesTranslator { - fun translateUpdates(updates: UMemoryUpdates): Result -} - -internal class UFlatUpdatesTranslator( - private val updateTranslator: UUpdateTranslator, -) : UUpdatesTranslator { - private val cache: IdentityHashMap, Result> = IdentityHashMap() - - override fun translateUpdates( - updates: UMemoryUpdates, - ): Result = - when (updates) { - is UFlatUpdates -> translateFlatUpdates(updates) - else -> error("This updates translator works only with UFlatUpdates") - } - - private fun translateFlatUpdates(updates: UFlatUpdates): Result { - val result = cache.getOrPut(updates) { - val node = updates.node ?: return@getOrPut updateTranslator.initialValue() - val accumulated = translateUpdates(node.next) - updateTranslator.applyUpdate(accumulated, node.update) - } - return result - } -} - -internal class UTreeUpdatesTranslator( - private val updateTranslator: UUpdateTranslator, -) : UUpdatesTranslator { - private val cache: IdentityHashMap, *>, Result> = IdentityHashMap() - - override fun translateUpdates(updates: UMemoryUpdates): Result { - require(updates is UTreeUpdates) { "This updates translator works only with UTreeUpdates" } - - return cache.getOrPut(updates.updates) { - Builder(updates).leftMostTranslate(updates.updates) - } - } - - private inner class Builder( - private val treeUpdates: UTreeUpdates, - ) { - private val emittedUpdates = hashSetOf>() - - fun leftMostTranslate(updates: RegionTree, *>): Result { - var result = cache[updates] - - if (result != null) { - return result - } - - val entryIterator = updates.entries.iterator() - if (!entryIterator.hasNext()) { - return updateTranslator.initialValue() - } - val (update, nextUpdates) = entryIterator.next().value - result = leftMostTranslate(nextUpdates) - result = updateTranslator.applyUpdate(result, update) - return notLeftMostTranslate(result, entryIterator) - } - - private fun notLeftMostTranslate( - accumulator: Result, - iterator: Iterator, Pair, RegionTree, *>>>>, - ): Result { - var accumulated = accumulator - while (iterator.hasNext()) { - val (reg, entry) = iterator.next() - val (update, tree) = entry - accumulated = notLeftMostTranslate(accumulated, tree.entries.iterator()) - - accumulated = addIfNeeded(accumulated, update, reg) - } - return accumulated - } - - private fun addIfNeeded(accumulated: Result, update: UUpdateNode, region: Region<*>): Result { - if (treeUpdates.checkWasCloned(update, region)) { - if (update in emittedUpdates) { - return accumulated - } - emittedUpdates += update - updateTranslator.applyUpdate(accumulated, update) - } - - return updateTranslator.applyUpdate(accumulated, update) - } - } -} From 8c7f2cb474f556cbf264e2690af469064692e070 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 11 Apr 2023 13:58:47 +0300 Subject: [PATCH 15/28] Fix: minor --- .../kotlin/org/usvm/HeapRefSplittingUtil.kt | 2 +- .../src/main/kotlin/org/usvm/MemoryUpdates.kt | 13 ++++---- .../test/kotlin/org/usvm/CompositionTest.kt | 32 ++++++------------- 3 files changed, 17 insertions(+), 30 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/HeapRefSplittingUtil.kt b/usvm-core/src/main/kotlin/org/usvm/HeapRefSplittingUtil.kt index c787bf22ba..0494fd6d12 100644 --- a/usvm-core/src/main/kotlin/org/usvm/HeapRefSplittingUtil.kt +++ b/usvm-core/src/main/kotlin/org/usvm/HeapRefSplittingUtil.kt @@ -139,7 +139,7 @@ internal inline fun UHeapRef.map( * Filters [ref] non-recursively with [predicate] and returns the result. A guard in the argument of the * [predicate] consists of a predicate from the root to the passed leaf. * - * It's guaranteed that [predicate] will be called exactly once on each leaf. + * Guarantees that [predicate] will be called exactly once on each leaf. * * @return A guarded expression with the guard indicating that any leaf on which [predicate] returns `false` * is inaccessible. `Null` is returned when all leafs match [predicate]. diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt index 4fd4ce5e86..bd0022bc62 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt @@ -3,7 +3,6 @@ package org.usvm import org.usvm.util.Region import org.usvm.util.RegionTree import org.usvm.util.emptyRegionTree -import java.util.IdentityHashMap /** * Represents a sequence of memory writes. @@ -473,12 +472,12 @@ data class UTreeUpdates, Sort : USort>( ) { fun fold(): Result = cache.getOrPut(updates) { - leftMostTranslate(updates) + leftMostFold(updates) } private val emittedUpdates = hashSetOf>() - private fun leftMostTranslate(updates: RegionTree, *>): Result { + private fun leftMostFold(updates: RegionTree, *>): Result { var result = cache[updates] if (result != null) { @@ -490,12 +489,12 @@ data class UTreeUpdates, Sort : USort>( return visitor.visitInitialValue() } val (update, nextUpdates) = entryIterator.next().value - result = leftMostTranslate(nextUpdates) + result = leftMostFold(nextUpdates) result = visitor.visitUpdate(result, update) - return notLeftMostTranslate(result, entryIterator) + return notLeftMostFold(result, entryIterator) } - private fun notLeftMostTranslate( + private fun notLeftMostFold( accumulator: Result, iterator: Iterator, Pair, RegionTree, *>>>>, ): Result { @@ -503,7 +502,7 @@ data class UTreeUpdates, Sort : USort>( while (iterator.hasNext()) { val (reg, entry) = iterator.next() val (update, tree) = entry - accumulated = notLeftMostTranslate(accumulated, tree.entries.iterator()) + accumulated = notLeftMostFold(accumulated, tree.entries.iterator()) accumulated = addIfNeeded(accumulated, update, reg) } diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index bd69c77ae1..a910345b1e 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -478,19 +478,13 @@ internal class CompositionTest { @Test fun testComposingAllocatedArray() = with(ctx) { - val heapModel = - UHeapModel( - mkConcreteHeapRef(NULL_ADDRESS), - mockk(), - persistentMapOf(), - persistentMapOf(), - persistentMapOf() - ) + val heapEvaluator: UReadOnlySymbolicHeap = mockk() + every { heapEvaluator.nullRef() } returns ctx.mkConcreteHeapRef(NULL_ADDRESS) val stackModel = URegistersStackModel(mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) - val model = UModelBase(this, stackModel, heapModel, mockk(), mockk()) + val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) val region = emptyAllocatedArrayRegion(mockk(), 1, bv32Sort) @@ -500,32 +494,26 @@ internal class CompositionTest { .write(mkRegisterReading(2, sizeSort), 3.toBv(), trueExpr) val reading = region.read(mkRegisterReading(0, sizeSort)) - val expr = model.eval(reading) + val expr = composer.compose(reading) assertSame(mkBv(2), expr) } @Test fun testNullRefRegionDefaultValue() = with(ctx) { - val concreteNull = mkConcreteHeapRef(NULL_ADDRESS) + val concreteNull = ctx.mkConcreteHeapRef(NULL_ADDRESS) + + val heapEvaluator: UReadOnlySymbolicHeap = mockk() - val heapModel = - UHeapModel( - concreteNull, - mockk(), - persistentMapOf(), - persistentMapOf(), - persistentMapOf() - ) + every { heapEvaluator.nullRef() } returns concreteNull val stackModel = URegistersStackModel(mapOf(0 to mkBv(0), 1 to mkBv(0), 2 to mkBv(2))) - val model = UModelBase(this, stackModel, heapModel, mockk(), mockk()) - + val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) val region = emptyAllocatedArrayRegion(mockk(), 1, addressSort) val reading = region.read(mkRegisterReading(0, sizeSort)) - val expr = model.eval(reading) + val expr = composer.compose(reading) assertSame(concreteNull, expr) } } From 294fe00d2b6cba4ba83ac0d89ad5a9bb48f020d6 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 11 Apr 2023 16:59:10 +0300 Subject: [PATCH 16/28] Fix: a bug with composition and add a test for it --- .../src/main/kotlin/org/usvm/Composition.kt | 36 +++++----- .../src/main/kotlin/org/usvm/MemoryRegions.kt | 16 ++++- .../src/main/kotlin/org/usvm/MemoryUpdates.kt | 10 +-- .../src/main/kotlin/org/usvm/UpdateNodes.kt | 65 ++++++++++--------- .../test/kotlin/org/usvm/CompositionTest.kt | 33 +++++++++- .../kotlin/org/usvm/MapCompositionTest.kt | 30 +++++---- 6 files changed, 120 insertions(+), 70 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/Composition.kt b/usvm-core/src/main/kotlin/org/usvm/Composition.kt index 9a917f1ba2..381cf080c3 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Composition.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Composition.kt @@ -1,18 +1,12 @@ package org.usvm -import org.ksmt.expr.KEqExpr -import org.ksmt.expr.KExpr -import org.ksmt.sort.KBoolSort -import org.ksmt.sort.KSort -import org.ksmt.utils.asExpr - @Suppress("MemberVisibilityCanBePrivate") open class UComposer( ctx: UContext, internal val stackEvaluator: URegistersStackEvaluator, internal val heapEvaluator: UReadOnlySymbolicHeap, internal val typeEvaluator: UTypeEvaluator, - internal val mockEvaluator: UMockEvaluator + internal val mockEvaluator: UMockEvaluator, ) : UExprTransformer(ctx) { open fun compose(expr: UExpr): UExpr = apply(expr) @@ -20,7 +14,7 @@ open class UComposer( error("You must override `transform` function in org.usvm.UComposer for ${expr::class}") override fun transform( - expr: URegisterReading + expr: URegisterReading, ): UExpr = with(expr) { stackEvaluator.eval(idx, sort) } override fun transform(expr: UHeapReading<*, *, *>): UExpr = @@ -30,7 +24,7 @@ open class UComposer( error("You must override `transform` function in org.usvm.UComposer for ${expr::class}") override fun transform( - expr: UIndexedMethodReturnValue + expr: UIndexedMethodReturnValue, ): UExpr = mockEvaluator.eval(expr) override fun transform(expr: UIsExpr): UBoolExpr = with(expr) { @@ -40,23 +34,25 @@ open class UComposer( fun , Key, Sort : USort> transformHeapReading( expr: UHeapReading, - key: Key + key: Key, ): UExpr = with(expr) { // if region.defaultValue != null, we don't need to apply updates to the heapEvaluator. expr.region // already contains ALL region writes, and underlying value (defaultValue) is defined, so we have all the // required information, and it cannot be refined. // Otherwise, the underlying value may be reified accordingly to the heapEvaluator - val mappedRegion = if (region.defaultValue == null) { - val instantiator = { key: Key, memoryRegion: UMemoryRegion -> - // Create a copy of this heap to avoid its modification - val heapToApplyUpdates = heapEvaluator.toMutableHeap() - memoryRegion.applyTo(heapToApplyUpdates) - region.regionId.read(heapToApplyUpdates, key) - } - region.map(this@UComposer, instantiator) - } else { - region.map(this@UComposer) + + val instantiatorFactory = object : UInstantiatorFactory { + override fun , Key, Sort : USort> build(): UInstantiator = + { key, memoryRegion -> + // Create a copy of this heap to avoid its modification + val heapToApplyUpdates = heapEvaluator.toMutableHeap() + memoryRegion.applyTo(heapToApplyUpdates) + memoryRegion.regionId.read(heapToApplyUpdates, key) + } } + + @Suppress("UNCHECKED_CAST") + val mappedRegion = region.map(this@UComposer, instantiatorFactory) val mappedKey = mappedRegion.regionId.keyMapper(this@UComposer)(key) mappedRegion.read(mappedKey) } diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt index 89a7661a06..7379a69477 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt @@ -9,6 +9,10 @@ import java.util.* //region Memory region +interface UInstantiatorFactory { + fun , Key, Sort : USort> build(): UInstantiator +} + /** * A typealias for a lambda that takes a key, a region and returns a reading from the region by the key. */ @@ -160,18 +164,26 @@ data class UMemoryRegion, Key, Sort : USort> * Note: after this operation a region returned as a result might be in `broken` state: * it might have both symbolic and concrete values as keys in it. */ + @Suppress("UNCHECKED_CAST") fun map( composer: UComposer, - instantiator: UInstantiator = this.instantiator, + instantiatorFactory: UInstantiatorFactory, ): UMemoryRegion { // Map the updates and the regionId @Suppress("UNCHECKED_CAST") val mappedRegionId = regionId.map(composer) as RegionId - val mappedUpdates = updates.map(regionId.keyMapper(composer), composer) + val mappedUpdates = updates.map(regionId.keyMapper(composer), composer, instantiatorFactory) // Note that we cannot use optimization with unchanged mappedUpdates and mappedDefaultValues here // since in a new region we might have an updated instantiator. // Therefore, we have to check their reference equality as well. + + val instantiator = if (defaultValue != null) { + this.instantiator + } else { + instantiatorFactory.build() + } + if (mappedUpdates === updates && mappedRegionId === regionId && instantiator === this.instantiator) { return this } diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt index bd0022bc62..4d4ee71c2f 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt @@ -41,7 +41,7 @@ interface UMemoryUpdates : Sequence> { * Returns a mapped [UMemoryRegion] using [keyMapper] and [composer]. * It is used in [UComposer] during memory composition. */ - fun map(keyMapper: KeyMapper, composer: UComposer): UMemoryUpdates + fun map(keyMapper: KeyMapper, composer: UComposer, instantiator: UInstantiatorFactory): UMemoryUpdates /** * @return Updates which express copying the slice of [fromRegion] guarded with @@ -155,11 +155,12 @@ class UFlatUpdates private constructor( override fun map( keyMapper: KeyMapper, composer: UComposer, + instantiator: UInstantiatorFactory ): UFlatUpdates { node ?: return this // Map the current node and the next values recursively - val mappedNode = node.update.map(keyMapper, composer) - val mappedNext = node.next.map(keyMapper, composer) + val mappedNode = node.update.map(keyMapper, composer, instantiator) + val mappedNext = node.next.map(keyMapper, composer, instantiator) // If nothing changed, return this updates if (mappedNode === node.update && mappedNext === node.next) { @@ -340,6 +341,7 @@ data class UTreeUpdates, Sort : USort>( override fun map( keyMapper: KeyMapper, composer: UComposer, + instantiator: UInstantiatorFactory ): UTreeUpdates { var mappedNodeFound = false @@ -348,7 +350,7 @@ data class UTreeUpdates, Sort : USort>( val mappedUpdates = updates.fold(initialEmptyTree) { mappedUpdatesTree, updateNodeWithRegion -> val (updateNode, oldRegion) = updateNodeWithRegion // Map current node - val mappedUpdateNode = updateNode.map(keyMapper, composer) + val mappedUpdateNode = updateNode.map(keyMapper, composer, instantiator) // Save information about whether something changed in the current node or not if (mappedUpdateNode !== updateNode) { diff --git a/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt b/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt index 99bbd67fca..80bd810185 100644 --- a/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt +++ b/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt @@ -9,7 +9,7 @@ import java.util.* * changes in processing of such nodes inside a core since we expect to have only two implementations: * [UPinpointUpdateNode] and [URangedUpdateNode]. */ -sealed interface UUpdateNode { +sealed interface UUpdateNode { /** * @return will the [key] get overwritten by this write operation in *any* possible concrete state, * assuming [precondition] holds, or not. @@ -19,7 +19,7 @@ sealed interface UUpdateNode { /** * @return will this write get overwritten by [update] operation in *any* possible concrete state or not. */ - fun isIncludedByUpdateConcretely(update: UUpdateNode): Boolean + fun isIncludedByUpdateConcretely(update: UUpdateNode): Boolean /** * @return Symbolic condition expressing that the address [key] got overwritten by this memory write operation. @@ -41,15 +41,15 @@ sealed interface UUpdateNode { */ fun split( key: Key, - predicate: (UExpr) -> Boolean, - matchingWrites: MutableList>>, + predicate: (UExpr) -> Boolean, + matchingWrites: MutableList>>, guardBuilder: GuardBuilder, - ): UUpdateNode? + ): UUpdateNode? /** * @return Value which has been written into the address [key] during this memory write operation. */ - fun value(key: Key): UExpr + fun value(key: Key): UExpr /** * Guard is a symbolic condition for this update. That is, this update is done only in states satisfying this guard. @@ -60,18 +60,22 @@ sealed interface UUpdateNode { * Returns a mapped update node using [keyMapper] and [composer]. * It is used in [UComposer] for composition. */ - fun map(keyMapper: KeyMapper, composer: UComposer): UUpdateNode + fun map( + keyMapper: KeyMapper, + composer: UComposer, + instantiatorFactory: UInstantiatorFactory + ): UUpdateNode } /** * Represents a single write of [value] into a memory address [key] */ -class UPinpointUpdateNode( +class UPinpointUpdateNode( val key: Key, - internal val value: UExpr, + internal val value: UExpr, private val keyEqualityComparer: (Key, Key) -> UBoolExpr, override val guard: UBoolExpr, -) : UUpdateNode { +) : UUpdateNode { override fun includesConcretely(key: Key, precondition: UBoolExpr) = this.key == key && (guard == guard.ctx.trueExpr || guard == precondition) // in fact, we can check less strict formulae: `precondition -> guard`, but it is too complex to compute. @@ -79,17 +83,17 @@ class UPinpointUpdateNode( override fun includesSymbolically(key: Key): UBoolExpr = guard.ctx.mkAnd(keyEqualityComparer(this.key, key), guard) - override fun isIncludedByUpdateConcretely(update: UUpdateNode): Boolean = + override fun isIncludedByUpdateConcretely(update: UUpdateNode): Boolean = update.includesConcretely(key, guard) - override fun value(key: Key): UExpr = this.value + override fun value(key: Key): UExpr = this.value override fun split( key: Key, - predicate: (UExpr) -> Boolean, - matchingWrites: MutableList>>, + predicate: (UExpr) -> Boolean, + matchingWrites: MutableList>>, guardBuilder: GuardBuilder, - ): UUpdateNode? { + ): UUpdateNode? { val ctx = value.ctx val nodeIncludesKey = includesSymbolically(key) // includes guard val nodeExcludesKey = ctx.mkNot(nodeIncludesKey) @@ -109,8 +113,9 @@ class UPinpointUpdateNode( override fun map( keyMapper: KeyMapper, - composer: UComposer - ): UPinpointUpdateNode { + composer: UComposer, + instantiatorFactory: UInstantiatorFactory + ): UPinpointUpdateNode { val mappedKey = keyMapper(key) val mappedValue = composer.compose(value) val mappedGuard = composer.compose(guard) @@ -194,15 +199,15 @@ sealed class UMemoryKeyConverter( * with values from memory region [region] read from range * of addresses [[keyConverter].convert([fromKey]) : [keyConverter].convert([toKey])] */ -class URangedUpdateNode, ArrayType, SrcKey, DstKey, ValueSort : USort>( +class URangedUpdateNode, ArrayType, SrcKey, DstKey, Sort : USort>( val fromKey: DstKey, val toKey: DstKey, - val region: UMemoryRegion, + val region: UMemoryRegion, private val concreteComparer: (DstKey, DstKey) -> Boolean, private val symbolicComparer: (DstKey, DstKey) -> UBoolExpr, val keyConverter: UMemoryKeyConverter, override val guard: UBoolExpr -) : UUpdateNode { +) : UUpdateNode { override fun includesConcretely(key: DstKey, precondition: UBoolExpr): Boolean = concreteComparer(fromKey, key) && concreteComparer(key, toKey) && (guard == guard.ctx.trueExpr || precondition == guard) // TODO: some optimizations here? @@ -216,18 +221,20 @@ class URangedUpdateNode, Array return ctx.mkAnd(leftIsLefter, rightIsRighter, guard) } - override fun isIncludedByUpdateConcretely(update: UUpdateNode): Boolean = + override fun isIncludedByUpdateConcretely(update: UUpdateNode): Boolean = update.includesConcretely(fromKey, guard) && update.includesConcretely(toKey, guard) - override fun value(key: DstKey): UExpr = region.read(keyConverter.convert(key)) + override fun value(key: DstKey): UExpr = region.read(keyConverter.convert(key)) + @Suppress("UNCHECKED_CAST") override fun map( - keyMapper: (DstKey) -> DstKey, - composer: UComposer - ): URangedUpdateNode { + keyMapper: KeyMapper, + composer: UComposer, + instantiatorFactory: UInstantiatorFactory + ): URangedUpdateNode { val mappedFromKey = keyMapper(fromKey) val mappedToKey = keyMapper(toKey) - val mappedRegion = region.map(composer) + val mappedRegion = region.map(composer, instantiatorFactory) val mappedKeyConverter = keyConverter.map(composer) val mappedGuard = composer.compose(guard) @@ -255,10 +262,10 @@ class URangedUpdateNode, Array override fun split( key: DstKey, - predicate: (UExpr) -> Boolean, - matchingWrites: MutableList>>, + predicate: (UExpr) -> Boolean, + matchingWrites: MutableList>>, guardBuilder: GuardBuilder, - ): UUpdateNode { + ): UUpdateNode { val ctx = guardBuilder.nonMatchingUpdatesGuard.ctx val nodeIncludesKey = includesSymbolically(key) // contains guard val nodeExcludesKey = ctx.mkNot(nodeIncludesKey) diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index a910345b1e..48fbbea2ca 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -17,7 +17,6 @@ import org.usvm.UAddressCounter.Companion.NULL_ADDRESS import kotlin.reflect.KClass import kotlin.test.assertEquals import kotlin.test.assertSame -import kotlinx.collections.immutable.persistentMapOf internal class CompositionTest { private lateinit var stackEvaluator: URegistersStackEvaluator @@ -477,7 +476,7 @@ internal class CompositionTest { } @Test - fun testComposingAllocatedArray() = with(ctx) { + fun testComposeAllocatedArray() = with(ctx) { val heapEvaluator: UReadOnlySymbolicHeap = mockk() every { heapEvaluator.nullRef() } returns ctx.mkConcreteHeapRef(NULL_ADDRESS) @@ -498,6 +497,36 @@ internal class CompositionTest { assertSame(mkBv(2), expr) } + @Test + fun testComposeRangedUpdate() = with(ctx) { + val heapEvaluator: USymbolicHeap = mockk() + every { heapEvaluator.nullRef() } returns ctx.mkConcreteHeapRef(NULL_ADDRESS) + val composedSymbolicHeapRef = ctx.mkConcreteHeapRef(-1) + val stackModel = URegistersStackModel(mapOf(0 to composedSymbolicHeapRef, 1 to ctx.mkBv(0))) + val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + + val symbolicRef = mkRegisterReading(0, addressSort) + + val arrayType = mockk() + val fromRegion = emptyInputArrayRegion(arrayType, bv32Sort) + + val concreteRef = mkConcreteHeapRef(0) + + val keyConverter = UInputToAllocatedKeyConverter(symbolicRef to mkBv(0), concreteRef to mkBv(0), mkBv(5)) + val concreteRegion = emptyAllocatedArrayRegion(arrayType, concreteRef.address, bv32Sort) + .copyRange(fromRegion, mkBv(0), mkBv(5), keyConverter, trueExpr) + + val idx = mkRegisterReading(1, sizeSort) + + val reading = concreteRegion.read(idx) + + every { heapEvaluator.toMutableHeap() } returns heapEvaluator + every { heapEvaluator.readArrayIndex(composedSymbolicHeapRef, ctx.mkBv(0), arrayType, bv32Sort) } returns mkBv(1) + + val expr = composer.compose(reading) + assertSame(mkBv(1), expr) + } + @Test fun testNullRefRegionDefaultValue() = with(ctx) { val concreteNull = ctx.mkConcreteHeapRef(NULL_ADDRESS) diff --git a/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt index df539f71b3..6f264e13f8 100644 --- a/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt @@ -8,7 +8,11 @@ import org.ksmt.expr.KExpr import org.ksmt.utils.mkConst import org.usvm.util.SetRegion import org.usvm.util.emptyRegionTree -import kotlin.test.* +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNotSame +import kotlin.test.assertNull +import kotlin.test.assertSame class MapCompositionTest { private lateinit var ctx: UContext @@ -51,7 +55,7 @@ class MapCompositionTest { every { composer.compose(value) } returns 1.toBv() every { composer.compose(mkTrue()) } returns mkTrue() - val composedUpdates = updatesToCompose.map(keyMapper = { composer.compose(it) }, composer) + val composedUpdates = updatesToCompose.map(keyMapper = { composer.compose(it) }, composer, mockk()) assert(composedUpdates.isEmpty()) } @@ -93,7 +97,7 @@ class MapCompositionTest { every { composer.compose(mkTrue()) } returns mkTrue() // ComposedUpdates contains only one update in a region {3} - val composedUpdates = updatesToCompose.map(keyMapper = { composer.compose(it) }, composer) + val composedUpdates = updatesToCompose.map(keyMapper = { composer.compose(it) }, composer, mockk()) assertFalse(composedUpdates.isEmpty()) @@ -120,7 +124,7 @@ class MapCompositionTest { every { composer.compose(value) } returns value every { composer.compose(guard) } returns guard - val mappedNode = updateNode.map({ k -> composer.compose(k) }, composer) + val mappedNode = updateNode.map({ k -> composer.compose(k) }, composer, mockk()) assertSame(expected = updateNode, actual = mappedNode) } @@ -139,7 +143,7 @@ class MapCompositionTest { every { composer.compose(value) } returns 1.toBv() every { composer.compose(guard) } returns mkTrue() - val mappedNode = updateNode.map({ k -> composer.compose(k) }, composer) + val mappedNode = updateNode.map({ k -> composer.compose(k) }, composer, mockk()) assertNotSame(illegal = updateNode, actual = mappedNode) assertSame(expected = composedKey, actual = mappedNode.key) @@ -175,7 +179,7 @@ class MapCompositionTest { every { region.map(composer, any()) } returns region every { composer.compose(guard) } returns guard - val mappedUpdateNode = updateNode.map({ k -> composer.compose((k)) }, composer) + val mappedUpdateNode = updateNode.map({ k -> composer.compose((k)) }, composer, mockk()) assertSame(expected = updateNode, actual = mappedUpdateNode) } @@ -213,7 +217,7 @@ class MapCompositionTest { every { region.map(composer, any()) } returns composedRegion every { composer.compose(guard) } returns composedGuard - val mappedUpdateNode = updateNode.map({ k -> composer.compose((k)) }, composer) + val mappedUpdateNode = updateNode.map({ k -> composer.compose((k)) }, composer, mockk()) assertNotSame(illegal = updateNode, actual = mappedUpdateNode) assertSame(expected = composedFromKey, actual = mappedUpdateNode.fromKey) @@ -230,7 +234,7 @@ class MapCompositionTest { { _, _ -> shouldNotBeCalled() } ) - val mappedUpdates = emptyUpdates.map({ k -> composer.compose(k) }, composer) + val mappedUpdates = emptyUpdates.map({ k -> composer.compose(k) }, composer, mockk()) assertSame(expected = emptyUpdates, actual = mappedUpdates) } @@ -255,7 +259,7 @@ class MapCompositionTest { every { composer.compose(sndValue) } returns sndValue every { composer.compose(mkTrue()) } returns mkTrue() - val mappedUpdates = flatUpdates.map({ k -> composer.compose(k) }, composer) + val mappedUpdates = flatUpdates.map({ k -> composer.compose(k) }, composer, mockk()) assertSame(expected = flatUpdates, actual = mappedUpdates) } @@ -285,7 +289,7 @@ class MapCompositionTest { every { composer.compose(sndValue) } returns composedSndValue every { composer.compose(mkTrue()) } returns mkTrue() - val mappedUpdates = flatUpdates.map({ k -> composer.compose(k) }, composer) + val mappedUpdates = flatUpdates.map({ k -> composer.compose(k) }, composer, mockk()) assertNotSame(illegal = flatUpdates, actual = mappedUpdates) @@ -323,7 +327,7 @@ class MapCompositionTest { every { composer.compose(sndValue) } returns sndValue every { composer.compose(mkTrue()) } returns mkTrue() - val mappedUpdates = treeUpdates.map({ k -> composer.compose(k) }, composer) + val mappedUpdates = treeUpdates.map({ k -> composer.compose(k) }, composer, mockk()) assertSame(expected = treeUpdates, actual = mappedUpdates) } @@ -356,7 +360,7 @@ class MapCompositionTest { every { composer.compose(sndValue) } returns composedSndValue every { composer.compose(mkTrue()) } returns mkTrue() - val mappedUpdates = treeUpdates.map({ k -> composer.compose(k) }, composer) + val mappedUpdates = treeUpdates.map({ k -> composer.compose(k) }, composer, mockk()) assertNotSame(illegal = treeUpdates, actual = mappedUpdates) @@ -376,4 +380,4 @@ class MapCompositionTest { fun shouldNotBeCalled(): T { error("Should not be called") -} \ No newline at end of file +} From 0789900cfc0e1feee50a67f5403743f31a60a675 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 11 Apr 2023 17:47:35 +0300 Subject: [PATCH 17/28] Refactor: minor refactorings and renamings --- .../src/main/kotlin/org/usvm/Composition.kt | 5 - .../main/kotlin/org/usvm/ExprTranslator.kt | 46 +++++++- .../src/main/kotlin/org/usvm/MemoryRegions.kt | 23 ++-- .../src/main/kotlin/org/usvm/ModelDecoder.kt | 93 +++++++++++++-- .../main/kotlin/org/usvm/RegionEvaluator.kt | 8 +- .../main/kotlin/org/usvm/TranslatorBuilder.kt | 108 ------------------ 6 files changed, 145 insertions(+), 138 deletions(-) delete mode 100644 usvm-core/src/main/kotlin/org/usvm/TranslatorBuilder.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/Composition.kt b/usvm-core/src/main/kotlin/org/usvm/Composition.kt index 381cf080c3..d55238206c 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Composition.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Composition.kt @@ -36,11 +36,6 @@ open class UComposer( expr: UHeapReading, key: Key, ): UExpr = with(expr) { - // if region.defaultValue != null, we don't need to apply updates to the heapEvaluator. expr.region - // already contains ALL region writes, and underlying value (defaultValue) is defined, so we have all the - // required information, and it cannot be refined. - // Otherwise, the underlying value may be reified accordingly to the heapEvaluator - val instantiatorFactory = object : UInstantiatorFactory { override fun , Key, Sort : USort> build(): UInstantiator = { key, memoryRegion -> diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index 2edb602cb9..797df446f0 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -3,11 +3,12 @@ package org.usvm import org.ksmt.sort.KArray2Sort import org.ksmt.sort.KArraySort import org.ksmt.sort.KArraySortBase +import org.ksmt.utils.cast import org.ksmt.utils.mkConst open class UExprTranslator constructor( override val ctx: UContext, -) : UExprTransformer(ctx), URegionTranslatorProvider { +) : UExprTransformer(ctx), URegionIdTranslatorFactory { open fun translate(expr: UExpr): UExpr = apply(expr) @@ -67,11 +68,11 @@ open class UExprTranslator constructor( region: UMemoryRegion, Key, Sort>, key: Key, ): UExpr { - val regionTranslator = provide(region.regionId) + val regionTranslator = buildTranslator(region.regionId) return regionTranslator.translateReading(region, key) } - // these functions implements URegionTranslatorProvider + // these functions implement URegionIdTranslatorFactory override fun visit( regionId: UInputFieldId, @@ -108,8 +109,41 @@ open class UExprTranslator constructor( val regionIdInitialValueProvider = URegionIdInitialValueProviderBase(onDefaultValuePresent = { translate(it) }) } -interface URegionTranslatorProvider : URegionIdVisitor> { - fun provide( +open class UCachingExprTranslator( + ctx: UContext, +) : UExprTranslator(ctx) { + + val registerIdxToTranslated = mutableMapOf>() + + override fun transform(expr: URegisterReading): UExpr = + registerIdxToTranslated.getOrPut(expr.idx) { + super.transform(expr) + }.cast() + + val indexedMethodReturnValueToTranslated = mutableMapOf, UExpr<*>>() + + override fun transform(expr: UIndexedMethodReturnValue): UExpr = + indexedMethodReturnValueToTranslated.getOrPut(expr.method to expr.callIndex) { + super.transform(expr) + }.cast() + + val translatedNullRef = super.translate(ctx.nullRef) + + override fun transform(expr: UNullRef): UExpr = translatedNullRef + + val regionIdToTranslator = + mutableMapOf, URegionTranslator, *, *, *>>() + + override fun buildTranslator( + regionId: URegionId, + ): URegionTranslator, Key, Sort, *> = + regionIdToTranslator.getOrPut(regionId) { + super.buildTranslator(regionId).cast() + }.cast() +} + +interface URegionIdTranslatorFactory : URegionIdVisitor> { + fun buildTranslator( regionId: URegionId, ): URegionTranslator, Key, Sort, *> { @Suppress("UNCHECKED_CAST") @@ -117,7 +151,7 @@ interface URegionTranslatorProvider : URegionIdVisitor> +typealias URegionIdInitialValueFactory = URegionIdVisitor> class URegionIdInitialValueProviderBase( val onDefaultValuePresent: (UExpr<*>) -> UExpr<*>, diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt index 7379a69477..5ab34d2cb2 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt @@ -9,15 +9,19 @@ import java.util.* //region Memory region -interface UInstantiatorFactory { - fun , Key, Sort : USort> build(): UInstantiator -} - /** * A typealias for a lambda that takes a key, a region and returns a reading from the region by the key. */ typealias UInstantiator = (key: Key, UMemoryRegion) -> UExpr +/** + * Used in [UComposer] for composing [UMemoryRegion]s onto heap. + */ +interface UInstantiatorFactory { + fun , Key, Sort : USort> build(): UInstantiator +} + + /** * A uniform unbounded slice of memory. Indexed by [Key], stores symbolic values. * @@ -174,16 +178,19 @@ data class UMemoryRegion, Key, Sort : USort> val mappedRegionId = regionId.map(composer) as RegionId val mappedUpdates = updates.map(regionId.keyMapper(composer), composer, instantiatorFactory) - // Note that we cannot use optimization with unchanged mappedUpdates and mappedDefaultValues here - // since in a new region we might have an updated instantiator. - // Therefore, we have to check their reference equality as well. - + // if region.defaultValue != null, we don't need to apply updates to the heapEvaluator. + // expr.region already contains ALL region writes, and underlying value (defaultValue) is defined, so we have + // all the required information, and it cannot be refined. + // Otherwise, the underlying value may be reified accordingly to the heapEvaluator val instantiator = if (defaultValue != null) { this.instantiator } else { instantiatorFactory.build() } + // Note that we cannot use optimization with unchanged mappedUpdates and mappedDefaultValues here + // since in a new region we might have an updated instantiator. + // Therefore, we have to check their reference equality as well. if (mappedUpdates === updates && mappedRegionId === regionId && instantiator === this.instantiator) { return this } diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt index 58b9b48703..78047b4f14 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt @@ -1,28 +1,46 @@ package org.usvm -import kotlinx.collections.immutable.PersistentMap -import kotlinx.collections.immutable.persistentMapOf import org.ksmt.expr.KExpr import org.ksmt.expr.KInterpretedValue import org.ksmt.solver.KModel import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.sort.KUninterpretedSort import org.ksmt.utils.asExpr +import org.ksmt.utils.cast import org.usvm.UAddressCounter.Companion.INITIAL_INPUT_ADDRESS import org.usvm.UAddressCounter.Companion.NULL_ADDRESS +import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.mutate +import kotlinx.collections.immutable.persistentMapOf interface UModelDecoder { fun decode(memory: Memory, model: KModel): Model } +fun buildDefaultTranslatorAndDecoder( + ctx: UContext, +): Pair, UModelDecoderBase> { + val translator = UCachingExprTranslator(ctx) + + val decoder = UModelDecoderBase( + translator.registerIdxToTranslated, + translator.indexedMethodReturnValueToTranslated, + translator.translatedNullRef, + translator.regionIdToTranslator.keys, + translator.regionIdInitialValueProvider, + ) + + return translator to decoder +} + typealias AddressesMapping = Map, UConcreteHeapRef> open class UModelDecoderBase( protected val registerIdxToTranslated: Map>, protected val indexedMethodReturnValueToTranslated: Map, UExpr<*>>, protected val translatedNullRef: UExpr, - protected val regionEvaluatorProviderBuilder: (KModel, AddressesMapping) -> URegionEvaluatorProvider, + protected val translatedRegionIds: Set>, + protected val regionIdInitialValueProvider: URegionIdInitialValueFactory, ) : UModelDecoder, UModelBase> { private val ctx: UContext = translatedNullRef.uctx @@ -81,10 +99,16 @@ open class UModelDecoderBase( model: KModel, addressesMapping: AddressesMapping, ): UHeapModel { - val regionEvaluator = regionEvaluatorProviderBuilder(model, addressesMapping) + val regionEvaluatorProvider = URegionEvaluatorForHeapModelFactory( + model, + addressesMapping, + translatedRegionIds, + regionIdInitialValueProvider, + ) + return UHeapModel( addressesMapping.getValue(translatedNullRef), - regionEvaluator, + regionEvaluatorProvider, persistentMapOf(), persistentMapOf(), persistentMapOf() @@ -130,7 +154,7 @@ class URegistersStackModel(private val registers: Map>) : class UHeapModel( private val nullRef: UConcreteHeapRef, - private val regionEvaluatorProvider: URegionEvaluatorProvider, + private val regionEvaluatorProvider: URegionEvaluatorFactory, private var resolvedInputFields: PersistentMap>, private var resolvedInputArrays: PersistentMap, out USort>>, private var resolvedInputLengths: PersistentMap>, @@ -323,4 +347,59 @@ fun UExpr.mapAddress( addressesMapping.getValue(asExpr(uctx.addressSort)).asExpr(sort) } else { this -} \ No newline at end of file +} + +private class URegionEvaluatorForHeapModelFactory( + model: KModel, + val addressesMapping: AddressesMapping, + translatedRegionIds: Set>, + regionIdInitialValueFactory: URegionIdInitialValueFactory, +) : URegionEvaluatorFactory, URegionIdVisitor> { + + private val evaluatorsForTranslatedRegions: MutableMap, URegionEvaluator<*, *>> + + init { + val regionEvaluatorProvider = + URegionEvaluatorFromKModelFactory(model, addressesMapping, regionIdInitialValueFactory) + + evaluatorsForTranslatedRegions = translatedRegionIds.associateWithTo(mutableMapOf()) { regionId -> + regionEvaluatorProvider.apply(regionId) + } + } + + override fun provide(regionId: URegionId): URegionEvaluator = + evaluatorsForTranslatedRegions.getOrElse(regionId) { + apply(regionId) + }.cast() + + override fun visit(regionId: UInputFieldId): U1DArrayEvaluator { + // If some region has a default value, it means that the region is an allocated one. + // All such regions must be processed earlier, and we won't have them here. + require(regionId.defaultValue == null) + // So, for these region we should take sample values for theis sorts. + val mappedConstValue = regionId.sort.sampleValue().mapAddress(addressesMapping) + return U1DArrayEvaluator(mappedConstValue) + } + + override fun visit(regionId: UAllocatedArrayId): URegionEvaluator<*, *> { + error("Allocated arrays should be evaluated implicitly") + } + + override fun visit(regionId: UInputArrayId): U2DArrayEvaluator { + // If some region has a default value, it means that the region is an allocated one. + // All such regions must be processed earlier, and we won't have them here. + require(regionId.defaultValue == null) + // So, for these region we should take sample values for theis sorts. + val mappedConstValue = regionId.sort.sampleValue().mapAddress(addressesMapping) + return U2DArrayEvaluator(mappedConstValue) + } + + override fun visit(regionId: UInputArrayLengthId): U1DArrayEvaluator { + // If some region has a default value, it means that the region is an allocated one. + // All such regions must be processed earlier, and we won't have them here. + require(regionId.defaultValue == null) + // So, for these region we should take sample values for theis sorts. + val mappedConstValue = regionId.sort.sampleValue().mapAddress(addressesMapping) + return U1DArrayEvaluator(mappedConstValue) + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt index 52d7911182..181d683e86 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt @@ -156,17 +156,17 @@ class U2DArrayEvaluator privat } } -interface URegionEvaluatorProvider { +interface URegionEvaluatorFactory { fun provide( regionId: URegionId, ): URegionEvaluator } -open class URegionEvaluatorFromKModelProvider( +open class URegionEvaluatorFromKModelFactory( private val model: KModel, private val mapping: Map, UConcreteHeapRef>, - private val regionIdInitialValueProvider: URegionIdInitialValueProvider, -) : URegionEvaluatorProvider, URegionIdVisitor> { + private val regionIdInitialValueProvider: URegionIdInitialValueFactory, +) : URegionEvaluatorFactory, URegionIdVisitor> { /** * Returns an evaluator for [regionId]. diff --git a/usvm-core/src/main/kotlin/org/usvm/TranslatorBuilder.kt b/usvm-core/src/main/kotlin/org/usvm/TranslatorBuilder.kt deleted file mode 100644 index d8d2490cbd..0000000000 --- a/usvm-core/src/main/kotlin/org/usvm/TranslatorBuilder.kt +++ /dev/null @@ -1,108 +0,0 @@ -package org.usvm - -import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue -import org.ksmt.utils.cast - -private class UCachingExprTranslator( - ctx: UContext, -) : UExprTranslator(ctx) { - - val registerIdxToTranslated = mutableMapOf>() - - override fun transform(expr: URegisterReading): UExpr = - registerIdxToTranslated.getOrPut(expr.idx) { - super.transform(expr) - }.cast() - - val indexedMethodReturnValueToTranslated = mutableMapOf, UExpr<*>>() - - override fun transform(expr: UIndexedMethodReturnValue): UExpr = - indexedMethodReturnValueToTranslated.getOrPut(expr.method to expr.callIndex) { - super.transform(expr) - }.cast() - - val translatedNullRef = super.translate(ctx.nullRef) - - override fun transform(expr: UNullRef): UExpr = translatedNullRef - - val regionIdToTranslator = - mutableMapOf, URegionTranslator, *, *, *>>() - - override fun provide(regionId: URegionId): URegionTranslator, Key, Sort, *> = - regionIdToTranslator.getOrPut(regionId) { - super.provide(regionId).cast() - }.cast() -} - -private class URegionEvaluatorForHeapModelProvider( - val mapping: AddressesMapping, - translatedRegionIds: Set>, - regionEvaluatorProvider: URegionEvaluatorFromKModelProvider, -) : URegionEvaluatorProvider, URegionIdVisitor> { - - private val evaluatorsForTranslatedRegions: MutableMap, URegionEvaluator<*, *>> - - init { - evaluatorsForTranslatedRegions = translatedRegionIds.associateWithTo(mutableMapOf()) { regionId -> - regionEvaluatorProvider.apply(regionId) - } - } - - override fun provide(regionId: URegionId): URegionEvaluator = - evaluatorsForTranslatedRegions.getOrElse(regionId) { - apply(regionId) - }.cast() - - override fun visit(regionId: UInputFieldId): U1DArrayEvaluator { - // If some region has a default value, it means that the region is an allocated one. - // All such regions must be processed earlier, and we won't have them here. - require(regionId.defaultValue == null) - // So, for these region we should take sample values for theis sorts. - val mappedConstValue = regionId.sort.sampleValue().mapAddress(mapping) - return U1DArrayEvaluator(mappedConstValue) - } - - override fun visit(regionId: UAllocatedArrayId): URegionEvaluator<*, *> { - error("Allocated arrays should be evaluated implicitly") - } - - override fun visit(regionId: UInputArrayId): U2DArrayEvaluator { - // If some region has a default value, it means that the region is an allocated one. - // All such regions must be processed earlier, and we won't have them here. - require(regionId.defaultValue == null) - // So, for these region we should take sample values for theis sorts. - val mappedConstValue = regionId.sort.sampleValue().mapAddress(mapping) - return U2DArrayEvaluator(mappedConstValue) - } - - override fun visit(regionId: UInputArrayLengthId): U1DArrayEvaluator { - // If some region has a default value, it means that the region is an allocated one. - // All such regions must be processed earlier, and we won't have them here. - require(regionId.defaultValue == null) - // So, for these region we should take sample values for theis sorts. - val mappedConstValue = regionId.sort.sampleValue().mapAddress(mapping) - return U1DArrayEvaluator(mappedConstValue) - } -} - -fun buildDefaultTranslatorAndDecoder( - ctx: UContext, -): Pair, UModelDecoderBase> { - val translator = UCachingExprTranslator(ctx) - - val decoder = UModelDecoderBase( - translator.registerIdxToTranslated, - translator.indexedMethodReturnValueToTranslated, - translator.translatedNullRef - ) { model, mapping -> - val regionEvaluatorProviderFromKModel = - URegionEvaluatorFromKModelProvider(model, mapping, translator.regionIdInitialValueProvider) - URegionEvaluatorForHeapModelProvider( - mapping, - translator.regionIdToTranslator.keys, - regionEvaluatorProviderFromKModel - ) - } - - return translator to decoder -} \ No newline at end of file From b9d31dc67990b731dea22ef6b1572fbe3412d4b9 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 11 Apr 2023 18:01:23 +0300 Subject: [PATCH 18/28] Fix: typo --- usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt index 78047b4f14..64c157cb23 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt @@ -255,7 +255,7 @@ class UHeapModel( value: UExpr, guard: UBoolExpr, ) { - // Since all values in the model are interpreted, we can check the exact guard8 value. + // Since all values in the model are interpreted, we can check the exact guard value. when { guard.isFalse -> return else -> require(guard.isTrue) From 359742a0ffc30e31a8aa6264ee9884dbb6cfd703 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 11 Apr 2023 18:27:47 +0300 Subject: [PATCH 19/28] Add: fix a small bug and add a test --- usvm-core/src/main/kotlin/org/usvm/Context.kt | 2 +- usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt | 4 +--- usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt | 2 +- usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt | 8 ++++---- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/Context.kt b/usvm-core/src/main/kotlin/org/usvm/Context.kt index 4aa64da7df..e544919630 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Context.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Context.kt @@ -22,7 +22,7 @@ open class UContext( val sizeSort: USizeSort = bv32Sort val zeroSize: USizeExpr = sizeSort.sampleValue() - val nullRef: USymbolicHeapRef = UNullRef(this) + val nullRef: UNullRef = UNullRef(this) fun mkNullRef(): USymbolicHeapRef { return nullRef diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index 797df446f0..61cc678e29 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -127,9 +127,7 @@ open class UCachingExprTranslator( super.transform(expr) }.cast() - val translatedNullRef = super.translate(ctx.nullRef) - - override fun transform(expr: UNullRef): UExpr = translatedNullRef + val translatedNullRef = super.transform(ctx.nullRef) val regionIdToTranslator = mutableMapOf, URegionTranslator, *, *, *>>() diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt index 64c157cb23..114fa15cef 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt @@ -59,7 +59,7 @@ open class UModelDecoderBase( val result = mutableMapOf, UConcreteHeapRef>() // Except the null value, it has the NULL_ADDRESS - result[ctx.nullRef] = ctx.mkConcreteHeapRef(NULL_ADDRESS) + result[interpretedNullRef] = ctx.mkConcreteHeapRef(NULL_ADDRESS) for (interpretedAddress in universe) { if (universe == interpretedNullRef) { diff --git a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt index b33dcb3157..471e80250f 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt @@ -1,11 +1,7 @@ package org.usvm -import io.mockk.mockk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.usvm.UAddressCounter.Companion.NULL_ADDRESS -import kotlin.test.assertSame -import kotlinx.collections.immutable.persistentMapOf class ModelDecodingTest { private lateinit var ctx: UContext @@ -15,4 +11,8 @@ class ModelDecodingTest { ctx = UContext() } + @Test + fun testSmoke() { + buildDefaultTranslatorAndDecoder(ctx) + } } From ea847995c7ac29bff0925c99b5accced38882d82 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 11 Apr 2023 20:07:29 +0300 Subject: [PATCH 20/28] Fix: workaround for an address sort sample value --- usvm-core/src/main/kotlin/org/usvm/Context.kt | 21 +++++++++++++------ usvm-core/src/main/kotlin/org/usvm/Heap.kt | 2 +- .../src/main/kotlin/org/usvm/MemoryRegions.kt | 2 +- usvm-core/src/main/kotlin/org/usvm/Mocks.kt | 2 +- .../src/main/kotlin/org/usvm/ModelDecoder.kt | 13 ++++++------ .../src/main/kotlin/org/usvm/PathCondition.kt | 17 +++++++-------- usvm-core/src/main/kotlin/org/usvm/Solver.kt | 10 ++++----- usvm-core/src/main/kotlin/org/usvm/State.kt | 2 +- .../test/kotlin/org/usvm/ModelDecodingTest.kt | 11 ++++++++-- .../test/kotlin/org/usvm/TranslationTest.kt | 2 +- 10 files changed, 49 insertions(+), 33 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/Context.kt b/usvm-core/src/main/kotlin/org/usvm/Context.kt index e544919630..964da60920 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Context.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Context.kt @@ -4,7 +4,7 @@ import org.ksmt.KAst import org.ksmt.KContext import org.ksmt.expr.KConst import org.ksmt.expr.KExpr -import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue +import org.ksmt.solver.model.DefaultValueSampler import org.ksmt.sort.KBoolSort import org.ksmt.sort.KSort import org.ksmt.sort.KUninterpretedSort @@ -152,13 +152,22 @@ open class UContext( UIsExpr(this, ref, type.cast()) }.cast() - override fun uninterpretedSortDefaultValue(sort: KUninterpretedSort): KExpr = - if (sort == addressSort) { - nullRef - } else { - super.uninterpretedSortDefaultValue(sort) + // TODO: this is a workaround until KSMT uninterpreted sort values is merged + class UDefaultValueSampler(ctx: UContext, sort: Sort) : DefaultValueSampler(ctx, sort) { + override fun visit(sort: KUninterpretedSort): KExpr { + val uctx = ctx as UContext + return if (sort == uctx.addressSort) { + uctx.nullRef.asExpr(this.sort) + } else { + super.visit(sort) + } } + } + companion object { + fun T.sampleValue(): KExpr = + accept(UDefaultValueSampler(uctx, this)) + } } val KAst.uctx diff --git a/usvm-core/src/main/kotlin/org/usvm/Heap.kt b/usvm-core/src/main/kotlin/org/usvm/Heap.kt index f7df31c941..a6dcd128ce 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Heap.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Heap.kt @@ -2,8 +2,8 @@ package org.usvm import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.persistentMapOf -import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.utils.asExpr +import org.usvm.UContext.Companion.sampleValue interface UReadOnlyHeap { fun readField(ref: Ref, field: Field, sort: Sort): Value diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt index 5ab34d2cb2..7a8ab4070e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt @@ -1,7 +1,7 @@ package org.usvm -import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.utils.asExpr +import org.usvm.UContext.Companion.sampleValue import org.usvm.util.SetRegion import org.usvm.util.emptyRegionTree import java.util.* diff --git a/usvm-core/src/main/kotlin/org/usvm/Mocks.kt b/usvm-core/src/main/kotlin/org/usvm/Mocks.kt index 10d150bb68..abdc044b49 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Mocks.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Mocks.kt @@ -4,8 +4,8 @@ import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf -import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.utils.asExpr +import org.usvm.UContext.Companion.sampleValue interface UMockEvaluator { fun eval(symbol: UMockSymbol): UExpr diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt index 114fa15cef..9ec7f3f290 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt @@ -3,12 +3,12 @@ package org.usvm import org.ksmt.expr.KExpr import org.ksmt.expr.KInterpretedValue import org.ksmt.solver.KModel -import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.sort.KUninterpretedSort import org.ksmt.utils.asExpr import org.ksmt.utils.cast import org.usvm.UAddressCounter.Companion.INITIAL_INPUT_ADDRESS import org.usvm.UAddressCounter.Companion.NULL_ADDRESS +import org.usvm.UContext.Companion.sampleValue import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.mutate import kotlinx.collections.immutable.persistentMapOf @@ -53,16 +53,17 @@ open class UModelDecoderBase( // Null is a special value that we want to translate in any case. val interpretedNullRef = model.eval(translatedNullRef, isComplete = true) - val universe = model.uninterpretedSortUniverse(ctx.addressSort) ?: return emptyMap() - // All the numbers are enumerated from the INITIAL_INPUT_ADDRESS to the Int.MIN_VALUE - var counter = INITIAL_INPUT_ADDRESS - val result = mutableMapOf, UConcreteHeapRef>() // Except the null value, it has the NULL_ADDRESS result[interpretedNullRef] = ctx.mkConcreteHeapRef(NULL_ADDRESS) + result[translatedNullRef] = ctx.mkConcreteHeapRef(NULL_ADDRESS) + + val universe = model.uninterpretedSortUniverse(ctx.addressSort) ?: return result + // All the numbers are enumerated from the INITIAL_INPUT_ADDRESS to the Int.MIN_VALUE + var counter = INITIAL_INPUT_ADDRESS for (interpretedAddress in universe) { - if (universe == interpretedNullRef) { + if (interpretedAddress == interpretedNullRef) { continue } result[interpretedAddress] = ctx.mkConcreteHeapRef(counter--) diff --git a/usvm-core/src/main/kotlin/org/usvm/PathCondition.kt b/usvm-core/src/main/kotlin/org/usvm/PathCondition.kt index 9207f865e6..582f44e572 100644 --- a/usvm-core/src/main/kotlin/org/usvm/PathCondition.kt +++ b/usvm-core/src/main/kotlin/org/usvm/PathCondition.kt @@ -8,22 +8,21 @@ interface UPathCondition: Sequence { fun add(constraint: UBoolExpr): UPathCondition } -class UPathConstraintsSet(private val ctx: UContext, - private val constraints: PersistentSet = persistentSetOf() -) - : UPathCondition +class UPathConstraintsSet( + private val constraints: PersistentSet = persistentSetOf() +) : UPathCondition { - fun contradiction() = - UPathConstraintsSet(ctx, persistentSetOf(ctx.mkFalse())) + fun contradiction(ctx: UContext) = + UPathConstraintsSet(persistentSetOf(ctx.mkFalse())) override val isFalse: Boolean get() = constraints.size == 1 && constraints.first() is UFalse override fun add(constraint: UBoolExpr): UPathCondition { - val notConstraint = constraint.ctx.mkNot(constraint) + val notConstraint = constraint.uctx.mkNot(constraint) if (constraints.contains(notConstraint)) - return contradiction() - return UPathConstraintsSet(ctx, constraints.add(constraint)) + return contradiction(constraint.uctx) + return UPathConstraintsSet(constraints.add(constraint)) } override fun iterator(): Iterator = constraints.iterator() diff --git a/usvm-core/src/main/kotlin/org/usvm/Solver.kt b/usvm-core/src/main/kotlin/org/usvm/Solver.kt index 2e074993de..95992c590f 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Solver.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Solver.kt @@ -18,10 +18,10 @@ abstract class USolver { } open class USolverBase( - val ctx: UContext, - val solver: KSolver<*>, - val translator: UExprTranslator, - val evaluator: UModelDecoder, UModelBase>, + protected val ctx: UContext, + protected val solver: KSolver<*>, + protected val translator: UExprTranslator, + protected val decoder: UModelDecoder, UModelBase>, ) : USolver, UPathCondition, UModelBase>() { override fun check(memory: UMemoryBase, pc: UPathCondition): USolverResult> { @@ -48,7 +48,7 @@ open class USolverBase( val model = solver.model().detach() solver.pop() - val uModel = evaluator.decode(memory, model) + val uModel = decoder.decode(memory, model) return USolverSat(uModel) } } diff --git a/usvm-core/src/main/kotlin/org/usvm/State.kt b/usvm-core/src/main/kotlin/org/usvm/State.kt index b235d46f22..1a11e23d0a 100644 --- a/usvm-core/src/main/kotlin/org/usvm/State.kt +++ b/usvm-core/src/main/kotlin/org/usvm/State.kt @@ -5,7 +5,7 @@ class UState( ctx: UContext, typeSystem: UTypeSystem, var callStack: UCallStack, - var pathCondition: UPathCondition = UPathConstraintsSet(ctx), + var pathCondition: UPathCondition = UPathConstraintsSet(), var memory: USymbolicMemory = UMemoryBase(ctx, typeSystem), var models: List ) {} diff --git a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt index 471e80250f..bbd560a8d4 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt @@ -1,7 +1,11 @@ package org.usvm +import io.mockk.mockk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.ksmt.solver.z3.KZ3Solver +import kotlin.test.assertIs +import kotlinx.collections.immutable.persistentSetOf class ModelDecodingTest { private lateinit var ctx: UContext @@ -12,7 +16,10 @@ class ModelDecodingTest { } @Test - fun testSmoke() { - buildDefaultTranslatorAndDecoder(ctx) + fun testSmoke(): Unit = with(ctx) { + val (translator, decoder) = buildDefaultTranslatorAndDecoder(ctx) + val solver = USolverBase(this, KZ3Solver(this), translator, decoder) + val status = solver.check(UMemoryBase(this, mockk()), UPathConstraintsSet(persistentSetOf(trueExpr))) + assertIs>>(status) } } diff --git a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt index 190b247aad..b56b9f17cf 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt @@ -5,9 +5,9 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.Test import org.ksmt.solver.KSolverStatus -import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.solver.z3.KZ3Solver import org.ksmt.utils.mkConst +import org.usvm.UContext.Companion.sampleValue import kotlin.test.assertSame class TranslationTest { From 3030bce5c8eb1e0d4cac940827a1ab5e12f1dd9c Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 17 Apr 2023 19:22:03 +0300 Subject: [PATCH 21/28] Refactor: URegionId, UMemoryRegions, etc. --- buildSrc/src/main/kotlin/Versions.kt | 2 +- .../src/main/kotlin/org/usvm/Composition.kt | 15 +- usvm-core/src/main/kotlin/org/usvm/Context.kt | 20 +- .../main/kotlin/org/usvm/ExprTranslator.kt | 14 +- .../src/main/kotlin/org/usvm/Expressions.kt | 4 +- usvm-core/src/main/kotlin/org/usvm/Heap.kt | 75 +++-- .../src/main/kotlin/org/usvm/MemoryRegions.kt | 100 +++---- .../src/main/kotlin/org/usvm/MemoryUpdates.kt | 42 ++- usvm-core/src/main/kotlin/org/usvm/Mocks.kt | 21 -- .../src/main/kotlin/org/usvm/ModelDecoder.kt | 258 ++++++++---------- .../{RegionEvaluator.kt => ModelRegions.kt} | 116 +++----- .../src/main/kotlin/org/usvm/RegionIds.kt | 96 +++++-- .../main/kotlin/org/usvm/RegionTranslator.kt | 23 +- .../main/kotlin/org/usvm/RegistersStack.kt | 1 - .../main/kotlin/org/usvm/UExprTransformer.kt | 4 +- .../src/main/kotlin/org/usvm/UpdateNodes.kt | 24 +- .../test/kotlin/org/usvm/CompositionTest.kt | 122 +++++++-- .../kotlin/org/usvm/MapCompositionTest.kt | 32 +-- .../test/kotlin/org/usvm/TranslationTest.kt | 2 +- 19 files changed, 500 insertions(+), 471 deletions(-) rename usvm-core/src/main/kotlin/org/usvm/{RegionEvaluator.kt => ModelRegions.kt} (55%) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 68f0825f99..bd678600a7 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -1,5 +1,5 @@ object Versions { - const val ksmt = "c9a39a7843" + const val ksmt = "0.5.0" const val collections = "0.3.5" const val coroutines = "1.6.4" const val jcdb = "a5c479bcf8" diff --git a/usvm-core/src/main/kotlin/org/usvm/Composition.kt b/usvm-core/src/main/kotlin/org/usvm/Composition.kt index d55238206c..bf9463c1b7 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Composition.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Composition.kt @@ -32,22 +32,11 @@ open class UComposer( typeEvaluator.evalIs(composedAddress, type) } - fun , Key, Sort : USort> transformHeapReading( + fun , Key, Sort : USort> transformHeapReading( expr: UHeapReading, key: Key, ): UExpr = with(expr) { - val instantiatorFactory = object : UInstantiatorFactory { - override fun , Key, Sort : USort> build(): UInstantiator = - { key, memoryRegion -> - // Create a copy of this heap to avoid its modification - val heapToApplyUpdates = heapEvaluator.toMutableHeap() - memoryRegion.applyTo(heapToApplyUpdates) - memoryRegion.regionId.read(heapToApplyUpdates, key) - } - } - - @Suppress("UNCHECKED_CAST") - val mappedRegion = region.map(this@UComposer, instantiatorFactory) + val mappedRegion = region.map(this@UComposer) val mappedKey = mappedRegion.regionId.keyMapper(this@UComposer)(key) mappedRegion.read(mappedKey) } diff --git a/usvm-core/src/main/kotlin/org/usvm/Context.kt b/usvm-core/src/main/kotlin/org/usvm/Context.kt index 964da60920..ce79300913 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Context.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Context.kt @@ -4,12 +4,14 @@ import org.ksmt.KAst import org.ksmt.KContext import org.ksmt.expr.KConst import org.ksmt.expr.KExpr -import org.ksmt.solver.model.DefaultValueSampler import org.ksmt.sort.KBoolSort import org.ksmt.sort.KSort +import org.ksmt.sort.KSortVisitor import org.ksmt.sort.KUninterpretedSort +import org.ksmt.utils.DefaultValueSampler import org.ksmt.utils.asExpr import org.ksmt.utils.cast +import org.ksmt.utils.sampleValue @Suppress("LeakingThis") open class UContext( @@ -152,21 +154,17 @@ open class UContext( UIsExpr(this, ref, type.cast()) }.cast() - // TODO: this is a workaround until KSMT uninterpreted sort values is merged - class UDefaultValueSampler(ctx: UContext, sort: Sort) : DefaultValueSampler(ctx, sort) { - override fun visit(sort: KUninterpretedSort): KExpr { - val uctx = ctx as UContext - return if (sort == uctx.addressSort) { - uctx.nullRef.asExpr(this.sort) + class UDefaultValueSampler(val uctx: UContext) : DefaultValueSampler(uctx) { + override fun visit(sort: KUninterpretedSort): KExpr<*> = + if (sort == uctx.addressSort) { + uctx.nullRef } else { super.visit(sort) } - } } - companion object { - fun T.sampleValue(): KExpr = - accept(UDefaultValueSampler(uctx, this)) + override fun mkDefaultValueSampler(): KSortVisitor> { + return UDefaultValueSampler(this) } } diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index 61cc678e29..6900cf10bd 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -65,7 +65,7 @@ open class UExprTranslator constructor( } open fun translateRegionReading( - region: UMemoryRegion, Key, Sort>, + region: USymbolicMemoryRegion, Key, Sort>, key: Key, ): UExpr { val regionTranslator = buildTranslator(region.regionId) @@ -130,11 +130,11 @@ open class UCachingExprTranslator( val translatedNullRef = super.transform(ctx.nullRef) val regionIdToTranslator = - mutableMapOf, URegionTranslator, *, *, *>>() + mutableMapOf, URegionTranslator, *, *, *>>() override fun buildTranslator( - regionId: URegionId, - ): URegionTranslator, Key, Sort, *> = + regionId: URegionId, + ): URegionTranslator, Key, Sort, *> = regionIdToTranslator.getOrPut(regionId) { super.buildTranslator(regionId).cast() }.cast() @@ -142,10 +142,10 @@ open class UCachingExprTranslator( interface URegionIdTranslatorFactory : URegionIdVisitor> { fun buildTranslator( - regionId: URegionId, - ): URegionTranslator, Key, Sort, *> { + regionId: URegionId, + ): URegionTranslator, Key, Sort, *> { @Suppress("UNCHECKED_CAST") - return apply(regionId) as URegionTranslator, Key, Sort, *> + return apply(regionId) as URegionTranslator, Key, Sort, *> } } diff --git a/usvm-core/src/main/kotlin/org/usvm/Expressions.kt b/usvm-core/src/main/kotlin/org/usvm/Expressions.kt index cc08159d36..2f130a7aa1 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Expressions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Expressions.kt @@ -127,9 +127,9 @@ class URegisterReading internal constructor( } } -abstract class UHeapReading, Key, Sort : USort>( +abstract class UHeapReading, Key, Sort : USort>( ctx: UContext, - val region: UMemoryRegion + val region: USymbolicMemoryRegion ) : USymbol(ctx) { override val sort: Sort get() = region.sort } diff --git a/usvm-core/src/main/kotlin/org/usvm/Heap.kt b/usvm-core/src/main/kotlin/org/usvm/Heap.kt index a6dcd128ce..1b54b81f59 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Heap.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Heap.kt @@ -3,11 +3,11 @@ package org.usvm import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.persistentMapOf import org.ksmt.utils.asExpr -import org.usvm.UContext.Companion.sampleValue +import org.ksmt.utils.sampleValue interface UReadOnlyHeap { fun readField(ref: Ref, field: Field, sort: Sort): Value - fun readArrayIndex(ref: Ref, index: SizeT, arrayType: ArrayType, elementSort: Sort): Value + fun readArrayIndex(ref: Ref, index: SizeT, arrayType: ArrayType, sort: Sort): Value fun readArrayLength(ref: Ref, arrayType: ArrayType): SizeT /** @@ -20,25 +20,25 @@ interface UReadOnlyHeap { typealias UReadOnlySymbolicHeap = UReadOnlyHeap, USizeExpr, Field, ArrayType, UBoolExpr> -class UEmptyHeap(private val ctx: UContext) : UReadOnlySymbolicHeap { - override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr = - sort.sampleValue() - - override fun readArrayIndex( - ref: UHeapRef, - index: USizeExpr, - arrayType: ArrayType, - elementSort: Sort, - ): UExpr = elementSort.sampleValue() - - override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType) = - ctx.zeroSize - - override fun toMutableHeap(): UHeap, USizeExpr, Field, ArrayType, UBoolExpr> = - URegionHeap(ctx) - - override fun nullRef(): UHeapRef = ctx.nullRef -} +//class UEmptyHeap(private val ctx: UContext) : UReadOnlySymbolicHeap { +// override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr = +// sort.sampleValue() +// +// override fun readArrayIndex( +// ref: UHeapRef, +// index: USizeExpr, +// arrayType: ArrayType, +// elementSort: Sort, +// ): UExpr = elementSort.sampleValue() +// +// override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType) = +// ctx.zeroSize +// +// override fun toMutableHeap(): UHeap, USizeExpr, Field, ArrayType, UBoolExpr> = +// URegionHeap(ctx) +// +// override fun nullRef(): UHeapRef = ctx.nullRef +//} interface UHeap : UReadOnlyHeap { @@ -47,7 +47,7 @@ interface UHeap : ref: Ref, index: SizeT, type: ArrayType, - elementSort: Sort, + sort: Sort, value: Value, guard: Guard, ) @@ -133,6 +133,23 @@ data class URegionHeap( inputLengths[arrayType] ?: emptyArrayLengthRegion(arrayType, ctx.sizeSort) + @Suppress("UNCHECKED_CAST", "UNUSED") + fun , Key, Sort : USort> getRegion(region: RegionId): USymbolicMemoryRegion = + // TODO with visitor? + when (region) { + is UInputFieldId<*, *> -> inputFieldRegion(region.field as Field, region.sort) + is UAllocatedArrayId<*, *> -> allocatedArrayRegion( + region.arrayType as ArrayType, + region.address, + region.sort + ) + + is UInputArrayLengthId<*> -> inputArrayLengthRegion(region.arrayType as ArrayType) + is UInputArrayId<*, *> -> inputArrayRegion(region.arrayType as ArrayType, region.sort) + else -> TODO("Implement symbolic collections") + } as USymbolicMemoryRegion + + override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr = ref.map( { concreteRef -> @@ -147,11 +164,11 @@ data class URegionHeap( ref: UHeapRef, index: USizeExpr, arrayType: ArrayType, - elementSort: Sort, + sort: Sort, ): UExpr = ref.map( - { concreteRef -> allocatedArrayRegion(arrayType, concreteRef.address, elementSort).read(index) }, - { symbolicRef -> inputArrayRegion(arrayType, elementSort).read(symbolicRef to index) } + { concreteRef -> allocatedArrayRegion(arrayType, concreteRef.address, sort).read(index) }, + { symbolicRef -> inputArrayRegion(arrayType, sort).read(symbolicRef to index) } ) override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType): USizeExpr = @@ -192,22 +209,22 @@ data class URegionHeap( ref: UHeapRef, index: USizeExpr, type: ArrayType, - elementSort: Sort, + sort: Sort, value: UExpr, guard: UBoolExpr, ) { - val valueToWrite = value.asExpr(elementSort) + val valueToWrite = value.asExpr(sort) withHeapRef( ref, guard, { (concreteRef, innerGuard) -> - val oldRegion = allocatedArrayRegion(type, concreteRef.address, elementSort) + val oldRegion = allocatedArrayRegion(type, concreteRef.address, sort) val newRegion = oldRegion.write(index, valueToWrite, innerGuard) allocatedArrays = allocatedArrays.put(concreteRef.address, newRegion) }, { (symbolicRef, innerGuard) -> - val oldRegion = inputArrayRegion(type, elementSort) + val oldRegion = inputArrayRegion(type, sort) val newRegion = oldRegion.write(symbolicRef to index, valueToWrite, innerGuard) inputArrays = inputArrays.put(type, newRegion) } diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt index 7a8ab4070e..1fa4a3ddb5 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt @@ -1,24 +1,21 @@ package org.usvm import org.ksmt.utils.asExpr -import org.usvm.UContext.Companion.sampleValue +import org.ksmt.utils.sampleValue import org.usvm.util.SetRegion import org.usvm.util.emptyRegionTree -import java.util.* //region Memory region - /** * A typealias for a lambda that takes a key, a region and returns a reading from the region by the key. */ -typealias UInstantiator = (key: Key, UMemoryRegion) -> UExpr +typealias UInstantiator = (key: Key, USymbolicMemoryRegion) -> UExpr -/** - * Used in [UComposer] for composing [UMemoryRegion]s onto heap. - */ -interface UInstantiatorFactory { - fun , Key, Sort : USort> build(): UInstantiator + +interface UMemoryRegion { + fun read(key: Key): UExpr + fun write(key: Key, value: UExpr, guard: UBoolExpr): UMemoryRegion } @@ -31,11 +28,10 @@ interface UInstantiatorFactory { * @property defaultValue describes the initial values for the region. If [defaultValue] equals `null` then this region * is filled with symbolics. */ -data class UMemoryRegion, Key, Sort : USort>( +data class USymbolicMemoryRegion, Key, Sort : USort>( val regionId: RegionId, val updates: UMemoryUpdates, - private val instantiator: UInstantiator, -) { +) : UMemoryRegion { // to save memory usage val sort: Sort get() = regionId.sort @@ -63,10 +59,10 @@ data class UMemoryRegion, Key, Sort : USort> this.copy(updates = updates) } - return instantiator(key, localizedRegion) + return regionId.instantiate(localizedRegion, key) } - fun read(key: Key): UExpr { + override fun read(key: Key): UExpr { if (sort == sort.uctx.addressSort) { // Here we split concrete heap addresses from symbolic ones to optimize further memory operations. return splittingRead(key) { it is UConcreteHeapRef } @@ -104,7 +100,7 @@ data class UMemoryRegion, Key, Sort : USort> return readingWithBubbledWrites } - fun write(key: Key, value: UExpr, guard: UBoolExpr): UMemoryRegion { + override fun write(key: Key, value: UExpr, guard: UBoolExpr): USymbolicMemoryRegion { assert(value.sort == sort) val newUpdates = if (sort == sort.uctx.addressSort) { @@ -129,14 +125,14 @@ data class UMemoryRegion, Key, Sort : USort> } /** - * Splits this [UMemoryRegion] on two parts: + * Splits this [USymbolicMemoryRegion] on two parts: * * Values of [UUpdateNode]s satisfying [predicate] are added to the [matchingWrites]. * * [UUpdateNode]s unsatisfying [predicate] remain in the result memory region. * * The [guardBuilder] is used to build guards for values added to [matchingWrites]. In the end, the [guardBuilder] * is updated and contains predicate indicating that the [key] can't be included in any of visited [UUpdateNode]s. * - * @return new [UMemoryRegion] without writes satisfying [predicate] or this [UMemoryRegion] if no matching writes + * @return new [USymbolicMemoryRegion] without writes satisfying [predicate] or this [USymbolicMemoryRegion] if no matching writes * were found. * @see [UMemoryUpdates.split], [splittingRead] */ @@ -145,8 +141,8 @@ data class UMemoryRegion, Key, Sort : USort> predicate: (UExpr) -> Boolean, matchingWrites: MutableList>>, guardBuilder: GuardBuilder, - ): UMemoryRegion { - // TODO: either check in UMemoryRegion constructor that we do not construct memory region with + ): USymbolicMemoryRegion { + // TODO: either check in USymbolicMemoryRegion constructor that we do not construct memory region with // non-null reference as default value, or implement splitting by default value. assert(defaultValue == null || !predicate(defaultValue)) @@ -168,35 +164,22 @@ data class UMemoryRegion, Key, Sort : USort> * Note: after this operation a region returned as a result might be in `broken` state: * it might have both symbolic and concrete values as keys in it. */ - @Suppress("UNCHECKED_CAST") fun map( composer: UComposer, - instantiatorFactory: UInstantiatorFactory, - ): UMemoryRegion { + ): USymbolicMemoryRegion { // Map the updates and the regionId - @Suppress("UNCHECKED_CAST") - val mappedRegionId = regionId.map(composer) as RegionId - val mappedUpdates = updates.map(regionId.keyMapper(composer), composer, instantiatorFactory) - - // if region.defaultValue != null, we don't need to apply updates to the heapEvaluator. - // expr.region already contains ALL region writes, and underlying value (defaultValue) is defined, so we have - // all the required information, and it cannot be refined. - // Otherwise, the underlying value may be reified accordingly to the heapEvaluator - val instantiator = if (defaultValue != null) { - this.instantiator - } else { - instantiatorFactory.build() - } + val mappedRegionId = regionId.map(composer) + val mappedUpdates = updates.map(regionId.keyMapper(composer), composer) // Note that we cannot use optimization with unchanged mappedUpdates and mappedDefaultValues here // since in a new region we might have an updated instantiator. // Therefore, we have to check their reference equality as well. - if (mappedUpdates === updates && mappedRegionId === regionId && instantiator === this.instantiator) { + if (mappedUpdates === updates && mappedRegionId === regionId) { return this } // Otherwise, construct a new region with mapped values and a new instantiator. - return UMemoryRegion(mappedRegionId, mappedUpdates, instantiator) + return USymbolicMemoryRegion(mappedRegionId, mappedUpdates) } @Suppress("UNCHECKED_CAST") @@ -205,7 +188,7 @@ data class UMemoryRegion, Key, Sort : USort> updates.forEach { when (it) { is UPinpointUpdateNode -> regionId.write(heap, it.key, it.value, it.guard) - is URangedUpdateNode<*, *, *, Key, Sort> -> { + is URangedUpdateNode<*, *, Key, Sort> -> { it.region.applyTo(heap) val (srcFromRef, srcFromIdx) = it.keyConverter.srcSymbolicArrayIndex @@ -224,12 +207,13 @@ data class UMemoryRegion, Key, Sort : USort> * with values from memory region [fromRegion] read from range * of addresses [[keyConverter].convert([fromKey]) : [keyConverter].convert([toKey])] */ - fun , SrcKey> copyRange( - fromRegion: UMemoryRegion, - fromKey: Key, toKey: Key, + fun , SrcKey> copyRange( + fromRegion: USymbolicMemoryRegion, + fromKey: Key, + toKey: Key, keyConverter: UMemoryKeyConverter, - guard: UBoolExpr, - ): UMemoryRegion { + guard: UBoolExpr + ): USymbolicMemoryRegion { val updatesCopy = updates.copyRange(fromRegion, fromKey, toKey, keyConverter, guard) return this.copy(updates = updatesCopy) } @@ -326,10 +310,10 @@ fun refIndexRangeRegion( idx2: USymbolicArrayIndex, ): UArrayIndexRegion = indexRangeRegion(idx1.second, idx2.second) -typealias UInputFieldRegion = UMemoryRegion, UHeapRef, Sort> -typealias UAllocatedArrayRegion = UMemoryRegion, USizeExpr, Sort> -typealias UInputArrayRegion = UMemoryRegion, USymbolicArrayIndex, Sort> -typealias UInputArrayLengthRegion = UMemoryRegion, UHeapRef, USizeSort> +typealias UInputFieldRegion = USymbolicMemoryRegion, UHeapRef, Sort> +typealias UAllocatedArrayRegion = USymbolicMemoryRegion, USizeExpr, Sort> +typealias UInputArrayRegion = USymbolicMemoryRegion, USymbolicArrayIndex, Sort> +typealias UInputArrayLengthRegion = USymbolicMemoryRegion, UHeapRef, USizeSort> typealias KeyMapper = (Key) -> Key @@ -351,10 +335,10 @@ fun emptyInputFieldRegion( field: Field, sort: Sort, ): UInputFieldRegion = - UMemoryRegion( - UInputFieldId(field, sort), + USymbolicMemoryRegion( + UInputFieldId(field, sort, contextHeap = null), UFlatUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic) - ) { key, region -> sort.uctx.mkInputFieldReading(region, key) } + ) fun emptyAllocatedArrayRegion( arrayType: ArrayType, @@ -365,10 +349,8 @@ fun emptyAllocatedArrayRegion( updates = emptyRegionTree(), ::indexRegion, ::indexRangeRegion, ::indexEq, ::indexLeConcrete, ::indexLeSymbolic ) - val regionId = UAllocatedArrayId(arrayType, address, sort, sort.sampleValue()) - return UMemoryRegion(regionId, updates) { key, region -> - sort.uctx.mkAllocatedArrayReading(region, key) - } + val regionId = UAllocatedArrayId(arrayType, address, sort, sort.sampleValue(), contextHeap = null) + return USymbolicMemoryRegion(regionId, updates) } fun emptyInputArrayRegion( @@ -379,18 +361,16 @@ fun emptyInputArrayRegion( updates = emptyRegionTree(), ::refIndexRegion, ::refIndexRangeRegion, ::refIndexEq, ::refIndexCmpConcrete, ::refIndexCmpSymbolic ) - return UMemoryRegion(UInputArrayId(arrayType, sort), updates) { pair, region -> - sort.uctx.mkInputArrayReading(region, pair.first, pair.second) - } + return USymbolicMemoryRegion(UInputArrayId(arrayType, sort, contextHeap = null), updates) } fun emptyArrayLengthRegion( arrayType: ArrayType, sizeSort: USizeSort, ): UInputArrayLengthRegion = - UMemoryRegion( - UInputArrayLengthId(arrayType, sizeSort), + USymbolicMemoryRegion( + UInputArrayLengthId(arrayType, sizeSort, contextHeap = null), UFlatUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic), - ) { ref, region -> sizeSort.uctx.mkInputArrayLengthReading(region, ref) } + ) //endregion diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt index 4d4ee71c2f..a140fe8f65 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt @@ -38,19 +38,19 @@ interface UMemoryUpdates : Sequence> { ): UMemoryUpdates /** - * Returns a mapped [UMemoryRegion] using [keyMapper] and [composer]. + * Returns a mapped [USymbolicMemoryRegion] using [keyMapper] and [composer]. * It is used in [UComposer] during memory composition. */ - fun map(keyMapper: KeyMapper, composer: UComposer, instantiator: UInstantiatorFactory): UMemoryUpdates + fun map(keyMapper: KeyMapper, composer: UComposer): UMemoryUpdates /** * @return Updates which express copying the slice of [fromRegion] guarded with * condition [guard]. * - * @see UMemoryRegion.copyRange + * @see USymbolicMemoryRegion.copyRange */ - fun , SrcKey> copyRange( - fromRegion: UMemoryRegion, + fun , SrcKey> copyRange( + fromRegion: USymbolicMemoryRegion, fromKey: Key, toKey: Key, keyConverter: UMemoryKeyConverter, @@ -115,8 +115,8 @@ class UFlatUpdates private constructor( symbolicCmp ) - override fun , SrcKey> copyRange( - fromRegion: UMemoryRegion, + override fun , SrcKey> copyRange( + fromRegion: USymbolicMemoryRegion, fromKey: Key, toKey: Key, keyConverter: UMemoryKeyConverter, @@ -154,13 +154,12 @@ class UFlatUpdates private constructor( override fun map( keyMapper: KeyMapper, - composer: UComposer, - instantiator: UInstantiatorFactory + composer: UComposer ): UFlatUpdates { node ?: return this // Map the current node and the next values recursively - val mappedNode = node.update.map(keyMapper, composer, instantiator) - val mappedNext = node.next.map(keyMapper, composer, instantiator) + val mappedNode = node.update.map(keyMapper, composer) + val mappedNext = node.next.map(keyMapper, composer) // If nothing changed, return this updates if (mappedNode === node.update && mappedNext === node.next) { @@ -257,13 +256,13 @@ data class UTreeUpdates, Sort : USort>( return this.copy(updates = newUpdates) } - override fun , SrcKey> copyRange( - fromRegion: UMemoryRegion, + override fun , SrcKey> copyRange( + fromRegion: USymbolicMemoryRegion, fromKey: Key, toKey: Key, keyConverter: UMemoryKeyConverter, - guard: UBoolExpr, - ): UMemoryUpdates { + guard: UBoolExpr + ): UTreeUpdates { val region = keyRangeToRegion(fromKey, toKey) val update = URangedUpdateNode(fromKey, toKey, fromRegion, concreteCmp, symbolicCmp, keyConverter, guard) val newUpdates = updates.write( @@ -280,7 +279,7 @@ data class UTreeUpdates, Sort : USort>( predicate: (UExpr) -> Boolean, matchingWrites: MutableList>>, guardBuilder: GuardBuilder, - ): UMemoryUpdates { + ): UTreeUpdates { // the suffix of the [updates], starting from the earliest update satisfying `predicate(update.value(key))` val updatesSuffix = mutableListOf?>() @@ -291,7 +290,7 @@ data class UTreeUpdates, Sort : USort>( fun applyUpdate(update: UUpdateNode) { val region = when (update) { is UPinpointUpdateNode -> keyToRegion(update.key) - is URangedUpdateNode<*, *, *, Key, Sort> -> keyRangeToRegion(update.fromKey, update.toKey) + is URangedUpdateNode<*, *, Key, Sort> -> keyRangeToRegion(update.fromKey, update.toKey) } splitUpdates = splitUpdates.write(region, update, keyFilter = { it.isIncludedByUpdateConcretely(update) }) } @@ -341,7 +340,6 @@ data class UTreeUpdates, Sort : USort>( override fun map( keyMapper: KeyMapper, composer: UComposer, - instantiator: UInstantiatorFactory ): UTreeUpdates { var mappedNodeFound = false @@ -350,7 +348,7 @@ data class UTreeUpdates, Sort : USort>( val mappedUpdates = updates.fold(initialEmptyTree) { mappedUpdatesTree, updateNodeWithRegion -> val (updateNode, oldRegion) = updateNodeWithRegion // Map current node - val mappedUpdateNode = updateNode.map(keyMapper, composer, instantiator) + val mappedUpdateNode = updateNode.map(keyMapper, composer) // Save information about whether something changed in the current node or not if (mappedUpdateNode !== updateNode) { @@ -369,8 +367,8 @@ data class UTreeUpdates, Sort : USort>( oldRegion.intersect(currentRegion) } - is URangedUpdateNode<*, *, *, Key, Sort> -> { - mappedUpdateNode as URangedUpdateNode<*, *, *, Key, Sort> + is URangedUpdateNode<*, *, Key, Sort> -> { + mappedUpdateNode as URangedUpdateNode<*, *, Key, Sort> val currentRegion = keyRangeToRegion(mappedUpdateNode.fromKey, mappedUpdateNode.toKey) oldRegion.intersect(currentRegion) } @@ -452,7 +450,7 @@ data class UTreeUpdates, Sort : USort>( // to the one stored in the current node. val initialRegion = when (update) { is UPinpointUpdateNode -> keyToRegion(update.key) - is URangedUpdateNode<*, *, *, Key, Sort> -> keyRangeToRegion(update.fromKey, update.toKey) + is URangedUpdateNode<*, *, Key, Sort> -> keyRangeToRegion(update.fromKey, update.toKey) } val wasCloned = initialRegion != region return wasCloned diff --git a/usvm-core/src/main/kotlin/org/usvm/Mocks.kt b/usvm-core/src/main/kotlin/org/usvm/Mocks.kt index abdc044b49..48738d5286 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Mocks.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Mocks.kt @@ -4,32 +4,11 @@ import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf -import org.ksmt.utils.asExpr -import org.usvm.UContext.Companion.sampleValue interface UMockEvaluator { fun eval(symbol: UMockSymbol): UExpr } -/** - * A model for an indexed mocker that stores mapping - * from mock symbols and invocation indices to expressions. - */ -class UIndexedMockModel( - private val values: Map, UExpr<*>>, -) : UMockEvaluator { - - override fun eval(symbol: UMockSymbol): UExpr { - require(symbol is UIndexedMethodReturnValue<*, Sort>) - - val sort = symbol.sort - @Suppress("UNCHECKED_CAST") - val key = symbol.method as Method to symbol.callIndex - - return values.getOrDefault(key, sort.sampleValue()).asExpr(sort) - } -} - interface UMocker : UMockEvaluator { fun call( method: Method, diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt index 9ec7f3f290..3e2d8bd476 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt @@ -8,10 +8,9 @@ import org.ksmt.utils.asExpr import org.ksmt.utils.cast import org.usvm.UAddressCounter.Companion.INITIAL_INPUT_ADDRESS import org.usvm.UAddressCounter.Companion.NULL_ADDRESS -import org.usvm.UContext.Companion.sampleValue import kotlinx.collections.immutable.PersistentMap -import kotlinx.collections.immutable.mutate -import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.toPersistentMap +import org.ksmt.utils.sampleValue interface UModelDecoder { fun decode(memory: Memory, model: KModel): Model @@ -39,7 +38,7 @@ open class UModelDecoderBase( protected val registerIdxToTranslated: Map>, protected val indexedMethodReturnValueToTranslated: Map, UExpr<*>>, protected val translatedNullRef: UExpr, - protected val translatedRegionIds: Set>, + protected val translatedRegionIds: Set>, protected val regionIdInitialValueProvider: URegionIdInitialValueFactory, ) : UModelDecoder, UModelBase> { private val ctx: UContext = translatedNullRef.uctx @@ -89,30 +88,58 @@ open class UModelDecoderBase( private fun decodeStack(model: KModel, addressesMapping: AddressesMapping): URegistersStackModel { val registers = registerIdxToTranslated.replaceUninterpretedConsts(model, addressesMapping) - return URegistersStackModel(registers) + return URegistersStackModel(addressesMapping.getValue(translatedNullRef), registers) } /** * Constructs a [UHeapModel] for a heap by provided [model] and [addressesMapping]. */ - @Suppress("UNCHECKED_CAST", "SafeCastWithReturn") + @Suppress("UNCHECKED_CAST") private fun decodeHeap( model: KModel, addressesMapping: AddressesMapping, ): UHeapModel { - val regionEvaluatorProvider = URegionEvaluatorForHeapModelFactory( - model, - addressesMapping, - translatedRegionIds, - regionIdInitialValueProvider, - ) + + val resolvedInputFields = mutableMapOf>() + val resolvedInputArrays = mutableMapOf>() + val resolvedInputArrayLengths = mutableMapOf>() + + translatedRegionIds.forEach { + when (it) { + is UInputFieldId<*, *> -> { + val resolved = UMemory1DArray( + regionIdInitialValueProvider.visit(it).cast(), + model, + addressesMapping + ) + resolvedInputFields[it.field as Field] = resolved + } + + is UInputArrayId<*, *> -> { + val resolved = UMemory2DArray( + regionIdInitialValueProvider.visit(it).cast(), + model, + addressesMapping + ) + resolvedInputArrays[it.arrayType as Type] = resolved + } + + is UInputArrayLengthId<*> -> { + val resolved = UMemory1DArray( + regionIdInitialValueProvider.visit(it).cast(), + model, + addressesMapping + ) + resolvedInputArrayLengths[it.arrayType as Type] = resolved + } + } + } return UHeapModel( addressesMapping.getValue(translatedNullRef), - regionEvaluatorProvider, - persistentMapOf(), - persistentMapOf(), - persistentMapOf() + resolvedInputFields.toPersistentMap(), + resolvedInputArrays.toPersistentMap(), + resolvedInputArrayLengths.toPersistentMap() ) } @@ -122,7 +149,7 @@ open class UModelDecoderBase( ): UIndexedMockModel { val values = indexedMethodReturnValueToTranslated.replaceUninterpretedConsts(model, addressesMapping) - return UIndexedMockModel(values) + return UIndexedMockModel(addressesMapping.getValue(translatedNullRef), values) } /** @@ -146,42 +173,77 @@ open class UModelDecoderBase( } -class URegistersStackModel(private val registers: Map>) : URegistersStackEvaluator { +class URegistersStackModel( + private val nullRef: UConcreteHeapRef, + private val registers: Map> +) : URegistersStackEvaluator { override fun eval( registerIndex: Int, sort: Sort, - ): UExpr = registers.getOrDefault(registerIndex, sort.sampleValue()).asExpr(sort) + ): UExpr = registers.getOrDefault(registerIndex, sort.sampleValue().nullAddress(nullRef)).asExpr(sort) +} + +/** + * A model for an indexed mocker that stores mapping + * from mock symbols and invocation indices to expressions. + */ +class UIndexedMockModel( + private val nullRef: UConcreteHeapRef, + private val values: Map, UExpr<*>>, +) : UMockEvaluator { + + override fun eval(symbol: UMockSymbol): UExpr { + require(symbol is UIndexedMethodReturnValue<*, Sort>) + + val sort = symbol.sort + @Suppress("UNCHECKED_CAST") + val key = symbol.method as Method to symbol.callIndex + + return values.getOrDefault(key, sort.sampleValue().nullAddress(nullRef)).asExpr(sort) + } } class UHeapModel( private val nullRef: UConcreteHeapRef, - private val regionEvaluatorProvider: URegionEvaluatorFactory, - private var resolvedInputFields: PersistentMap>, - private var resolvedInputArrays: PersistentMap, out USort>>, - private var resolvedInputLengths: PersistentMap>, + private var resolvedInputFields: PersistentMap>, + private var resolvedInputArrays: PersistentMap>, + private var resolvedInputLengths: PersistentMap>, ) : USymbolicHeap { + + @Suppress("UNCHECKED_CAST") + private fun inputFieldRegion(field: Field, sort: Sort): UMemoryRegion = + resolvedInputFields.getOrElse(field) { + UMemory1DArray(sort.sampleValue().nullAddress(nullRef)) + } as UMemoryRegion + + @Suppress("UNCHECKED_CAST") + private fun inputArrayRegion(arrayType: ArrayType, sort: Sort): UMemoryRegion = + resolvedInputArrays.getOrElse(arrayType) { + UMemory2DArray(sort.sampleValue().nullAddress(nullRef)) + } as UMemoryRegion + + private fun inputLengthRegion(arrayType: ArrayType, sort: USizeSort): UMemoryRegion = + resolvedInputLengths.getOrElse(arrayType) { + UMemory1DArray(sort.sampleValue()) + } + + override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr { // All the expressions in the model are interpreted, therefore, they must // have concrete addresses. Moreover, the model known only about input values // which have addresses less or equal than INITIAL_INPUT_ADDRESS require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - @Suppress("UNCHECKED_CAST") - val regionEvaluator = resolvedInputFields.getOrElse(field) { - val regionId = UInputFieldId(field, sort) - val evaluator = regionEvaluatorProvider.provide(regionId) - resolvedInputFields = resolvedInputFields.put(field, evaluator) - evaluator - } as URegionEvaluator - - return regionEvaluator.select(ref).asExpr(sort) + val region = inputFieldRegion(field, sort) + + return region.read(ref) } override fun readArrayIndex( ref: UHeapRef, index: USizeExpr, arrayType: ArrayType, - elementSort: Sort, + sort: Sort, ): UExpr { // All the expressions in the model are interpreted, therefore, they must // have concrete addresses. Moreover, the model known only about input values @@ -190,15 +252,9 @@ class UHeapModel( val key = ref to index - @Suppress("UNCHECKED_CAST") - val regionEvaluator = resolvedInputArrays.getOrElse(arrayType) { - val regionId = UInputArrayId(arrayType, elementSort) - val evaluator = regionEvaluatorProvider.provide(regionId) - resolvedInputArrays = resolvedInputArrays.put(arrayType, evaluator) - evaluator - } as URegionEvaluator, Sort> - - return regionEvaluator.select(key).asExpr(elementSort) + val region = inputArrayRegion(arrayType, sort) + + return region.read(key) } override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType): USizeExpr { @@ -207,14 +263,9 @@ class UHeapModel( // which have addresses less or equal than INITIAL_INPUT_ADDRESS require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - val regionEvaluator = resolvedInputLengths.getOrElse(arrayType) { - val regionId = UInputArrayLengthId(arrayType, ref.uctx.sizeSort) - val evaluator = regionEvaluatorProvider.provide(regionId) - resolvedInputLengths = resolvedInputLengths.put(arrayType, evaluator) - evaluator - } + val region = inputLengthRegion(arrayType, ref.uctx.sizeSort) - return regionEvaluator.select(ref) + return region.read(ref) } override fun writeField( @@ -237,22 +288,16 @@ class UHeapModel( // which have addresses less or equal than INITIAL_INPUT_ADDRESS require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - @Suppress("UNCHECKED_CAST") - val regionEvaluator = resolvedInputFields.getOrElse(field) { - val regionId = UInputFieldId(field, sort) - val evaluator = regionEvaluatorProvider.provide(regionId) - resolvedInputFields = resolvedInputFields.put(field, evaluator) - evaluator - } as URegionEvaluator - - regionEvaluator.write(ref, valueToWrite) + val region = inputFieldRegion(field, sort).write(ref, valueToWrite, guard) + resolvedInputFields = resolvedInputFields.put(field, region) + } override fun writeArrayIndex( ref: UHeapRef, index: USizeExpr, type: ArrayType, - elementSort: Sort, + sort: Sort, value: UExpr, guard: UBoolExpr, ) { @@ -268,17 +313,10 @@ class UHeapModel( require(index is KInterpretedValue) require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - val valueToWrite = value.asExpr(elementSort) + val valueToWrite = value.asExpr(sort) - @Suppress("UNCHECKED_CAST") - val regionEvaluator = resolvedInputArrays.getOrElse(type) { - val regionId = UInputArrayId(type, elementSort) - val evaluator = regionEvaluatorProvider.provide(regionId) - resolvedInputArrays = resolvedInputArrays.put(type, evaluator) - evaluator - } as URegionEvaluator, Sort> - - regionEvaluator.write(ref to index, valueToWrite) + val region = inputArrayRegion(type, sort).write(ref to index, valueToWrite, guard) + resolvedInputArrays = resolvedInputArrays.put(type, region) } override fun writeArrayLength(ref: UHeapRef, size: USizeExpr, arrayType: ArrayType) { @@ -288,14 +326,8 @@ class UHeapModel( require(size is KInterpretedValue) require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - val reginEvaluator = resolvedInputLengths.getOrElse(arrayType) { - val regionId = UInputArrayLengthId(arrayType, ref.uctx.sizeSort) - val evaluator = regionEvaluatorProvider.provide(regionId) - resolvedInputLengths = resolvedInputLengths.put(arrayType, evaluator) - evaluator - } - - reginEvaluator.write(ref, size) + val region = inputLengthRegion(arrayType, size.sort).write(ref, size, size.sort.uctx.trueExpr) + resolvedInputLengths = resolvedInputLengths.put(arrayType, region) } override fun memcpy( @@ -323,10 +355,9 @@ class UHeapModel( override fun clone(): UHeapModel = UHeapModel( nullRef, - regionEvaluatorProvider, - resolvedInputFields.mapValues { evaluator -> evaluator.clone() }, - resolvedInputArrays.mapValues { evaluator -> evaluator.clone() }, - resolvedInputLengths.mapValues { evaluator -> evaluator.clone() }, + resolvedInputFields, + resolvedInputArrays, + resolvedInputLengths, ) override fun nullRef(): UConcreteHeapRef = nullRef @@ -334,8 +365,12 @@ class UHeapModel( override fun toMutableHeap(): UHeapModel = clone() } -inline private fun PersistentMap.mapValues(crossinline mapper: (V) -> V): PersistentMap = - mutate { old -> old.replaceAll { _, value -> mapper(value) } } +fun UExpr.nullAddress(nullRef: UConcreteHeapRef): UExpr = + if (this == uctx.nullRef) { + nullRef.asExpr(sort) + } else { + this + } /** * If [this] value is an instance of address expression, returns @@ -348,59 +383,4 @@ fun UExpr.mapAddress( addressesMapping.getValue(asExpr(uctx.addressSort)).asExpr(sort) } else { this -} - -private class URegionEvaluatorForHeapModelFactory( - model: KModel, - val addressesMapping: AddressesMapping, - translatedRegionIds: Set>, - regionIdInitialValueFactory: URegionIdInitialValueFactory, -) : URegionEvaluatorFactory, URegionIdVisitor> { - - private val evaluatorsForTranslatedRegions: MutableMap, URegionEvaluator<*, *>> - - init { - val regionEvaluatorProvider = - URegionEvaluatorFromKModelFactory(model, addressesMapping, regionIdInitialValueFactory) - - evaluatorsForTranslatedRegions = translatedRegionIds.associateWithTo(mutableMapOf()) { regionId -> - regionEvaluatorProvider.apply(regionId) - } - } - - override fun provide(regionId: URegionId): URegionEvaluator = - evaluatorsForTranslatedRegions.getOrElse(regionId) { - apply(regionId) - }.cast() - - override fun visit(regionId: UInputFieldId): U1DArrayEvaluator { - // If some region has a default value, it means that the region is an allocated one. - // All such regions must be processed earlier, and we won't have them here. - require(regionId.defaultValue == null) - // So, for these region we should take sample values for theis sorts. - val mappedConstValue = regionId.sort.sampleValue().mapAddress(addressesMapping) - return U1DArrayEvaluator(mappedConstValue) - } - - override fun visit(regionId: UAllocatedArrayId): URegionEvaluator<*, *> { - error("Allocated arrays should be evaluated implicitly") - } - - override fun visit(regionId: UInputArrayId): U2DArrayEvaluator { - // If some region has a default value, it means that the region is an allocated one. - // All such regions must be processed earlier, and we won't have them here. - require(regionId.defaultValue == null) - // So, for these region we should take sample values for theis sorts. - val mappedConstValue = regionId.sort.sampleValue().mapAddress(addressesMapping) - return U2DArrayEvaluator(mappedConstValue) - } - - override fun visit(regionId: UInputArrayLengthId): U1DArrayEvaluator { - // If some region has a default value, it means that the region is an allocated one. - // All such regions must be processed earlier, and we won't have them here. - require(regionId.defaultValue == null) - // So, for these region we should take sample values for theis sorts. - val mappedConstValue = regionId.sort.sampleValue().mapAddress(addressesMapping) - return U1DArrayEvaluator(mappedConstValue) - } -} +} \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt b/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt similarity index 55% rename from usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt rename to usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt index 181d683e86..11bb6c64d5 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionEvaluator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt @@ -1,5 +1,8 @@ package org.usvm +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.toPersistentMap import org.ksmt.expr.KArray2Store import org.ksmt.expr.KArrayConst import org.ksmt.expr.KArrayStore @@ -7,21 +10,15 @@ import org.ksmt.expr.KExpr import org.ksmt.solver.KModel import org.ksmt.sort.KArray2Sort import org.ksmt.sort.KArraySort -import org.ksmt.utils.cast -interface URegionEvaluator { - fun select(key: Key): UExpr - fun write(key: Key, expr: UExpr) - fun clone(): URegionEvaluator -} /** * A specific evaluator for one-dimensional regions generalized by a single expression of a [KeySort]. */ -class U1DArrayEvaluator private constructor( - private val values: MutableMap, UExpr>, +class UMemory1DArray private constructor( + private val values: PersistentMap, UExpr>, private val constValue: UExpr, -) : URegionEvaluator, Sort> { +) : UMemoryRegion, Sort> { /** * A constructor that is used in cases when we try to evaluate @@ -29,19 +26,23 @@ class U1DArrayEvaluator private constructor( */ constructor( mappedConstValue: UExpr, - ) : this(mutableMapOf(), mappedConstValue) - - override fun select(key: KExpr): UExpr = values.getOrDefault(key, constValue) - - override fun write(key: KExpr, expr: UExpr) { - values[key] = expr + ) : this(persistentMapOf(), mappedConstValue) + + override fun read(key: KExpr): UExpr = values.getOrDefault(key, constValue) + + override fun write( + key: KExpr, + value: UExpr, + guard: UBoolExpr + ): UMemoryRegion, Sort> { + when { + guard.isFalse -> return this + else -> require(guard.isTrue) + } + val newMap = values.put(key, value) + return UMemory1DArray(newMap, constValue) } - override fun clone(): U1DArrayEvaluator = - U1DArrayEvaluator( - values.toMutableMap(), - constValue - ) companion object { /** @@ -55,7 +56,7 @@ class U1DArrayEvaluator private constructor( initialValue: KExpr>, model: KModel, mapping: Map, - ): U1DArrayEvaluator { + ): UMemory1DArray { // Since the model contains only information about values we got from the outside, // we can translate and ask only about an initial value for the region. // All other values should be resolved earlier without asking the model. @@ -80,7 +81,7 @@ class U1DArrayEvaluator private constructor( val constValue = valueCopy.value.mapAddress(mapping) val values = stores - return U1DArrayEvaluator(values, constValue) + return UMemory1DArray(values.toPersistentMap(), constValue) } } } @@ -89,32 +90,35 @@ class U1DArrayEvaluator private constructor( * A specific evaluator for two-dimensional regions generalized be a pair * of two expressions with [Key1Sort] and [Key2Sort] sorts. */ -class U2DArrayEvaluator private constructor( - val values: MutableMap, UExpr>, UExpr>, +class UMemory2DArray private constructor( + val values: PersistentMap, UExpr>, UExpr>, val constValue: UExpr, -) : URegionEvaluator, KExpr>, Sort> { +) : UMemoryRegion, KExpr>, Sort> { /** * A constructor that is used in cases when we try to evaluate * an expression from a region that was never translated. */ constructor( mappedConstValue: UExpr, - ) : this(mutableMapOf(), mappedConstValue) + ) : this(persistentMapOf(), mappedConstValue) - override fun select(key: Pair, KExpr>): UExpr { + override fun read(key: Pair, KExpr>): UExpr { return values.getOrDefault(key, constValue) } - override fun write(key: Pair, KExpr>, expr: UExpr) { - values[key] = expr + override fun write( + key: Pair, KExpr>, + value: UExpr, + guard: UBoolExpr + ): UMemoryRegion, KExpr>, Sort> { + when { + guard.isFalse -> return this + else -> require(guard.isTrue) + } + val newMap = values.put(key, value) + return UMemory2DArray(newMap, constValue) } - override fun clone(): U2DArrayEvaluator = - U2DArrayEvaluator( - values.toMutableMap(), - constValue - ) - companion object { /** * A constructor that is used in regular cases for a region @@ -127,7 +131,7 @@ class U2DArrayEvaluator privat initialValue: KExpr>, model: KModel, mapping: Map, - ): U2DArrayEvaluator { + ): UMemory2DArray { val evaluatedArray = model.eval(initialValue, isComplete = true) var valueCopy = evaluatedArray @@ -151,45 +155,7 @@ class U2DArrayEvaluator privat val constValue = valueCopy.value.mapAddress(mapping) val values = stores - return U2DArrayEvaluator(values, constValue) + return UMemory2DArray(values.toPersistentMap(), constValue) } } -} - -interface URegionEvaluatorFactory { - fun provide( - regionId: URegionId, - ): URegionEvaluator -} - -open class URegionEvaluatorFromKModelFactory( - private val model: KModel, - private val mapping: Map, UConcreteHeapRef>, - private val regionIdInitialValueProvider: URegionIdInitialValueFactory, -) : URegionEvaluatorFactory, URegionIdVisitor> { - - /** - * Returns an evaluator for [regionId]. - * Note that it is a translator-specific evaluator that - * knows how to decode values from the [model]. - * - * [mapping] contains information about matching expressions of the - * address sort and their concrete (evaluated) representations. - */ - @Suppress("UNCHECKED_CAST") - override fun provide( - regionId: URegionId, - ): URegionEvaluator = apply(regionId) as URegionEvaluator - - override fun visit(regionId: UInputFieldId): URegionEvaluator = - U1DArrayEvaluator(regionIdInitialValueProvider.apply(regionId).cast(), model, mapping) - - override fun visit(regionId: UAllocatedArrayId): URegionEvaluator = - U1DArrayEvaluator(regionIdInitialValueProvider.apply(regionId).cast(), model, mapping) - - override fun visit(regionId: UInputArrayId): URegionEvaluator = - U2DArrayEvaluator(regionIdInitialValueProvider.apply(regionId).cast(), model, mapping) - - override fun visit(regionId: UInputArrayLengthId): URegionEvaluator = - U1DArrayEvaluator(regionIdInitialValueProvider.apply(regionId).cast(), model, mapping) } \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt index 3692144b0e..28d9aa0b88 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt @@ -5,9 +5,10 @@ import org.ksmt.utils.asExpr /** * An interface that represents any possible type of regions that can be used in the memory. */ -interface URegionId { +interface URegionId> { val sort: Sort val defaultValue: UExpr? + fun instantiate(region: USymbolicMemoryRegion<@UnsafeVariance RegionId, Key, Sort>, key: Key): UExpr fun read( heap: UReadOnlySymbolicHeap, @@ -23,15 +24,16 @@ interface URegionId { fun keyMapper(transformer: UExprTransformer): KeyMapper - fun map(composer: UComposer): URegionId + fun map(composer: UComposer): RegionId fun accept(visitor: URegionIdVisitor): R } interface URegionIdVisitor { - fun apply(regionId: URegionId): R = regionId.accept(this) + fun > apply(regionId: URegionId): R = + regionId.accept(this) - fun visit(regionId: URegionId): Any? = + fun > visit(regionId: URegionId): Any? = error("You must provide visit implementation for ${regionId::class} in ${this::class}") fun visit(regionId: UInputFieldId): R @@ -49,8 +51,18 @@ interface URegionIdVisitor { data class UInputFieldId internal constructor( val field: Field, override val sort: Sort, -) : URegionId { + val contextHeap: USymbolicHeap?, +) : URegionId> { override val defaultValue: UExpr? get() = null + override fun instantiate( + region: USymbolicMemoryRegion, UHeapRef, Sort>, + key: UHeapRef + ): UExpr = if (contextHeap == null) { + sort.uctx.mkInputFieldReading(region.copy(regionId = UInputFieldId(field, sort, null)), key) + } else { + region.applyTo(contextHeap) + read(contextHeap, key) + } @Suppress("UNCHECKED_CAST") override fun read( @@ -70,8 +82,11 @@ data class UInputFieldId internal constructor( transformer: UExprTransformer, ): KeyMapper = { transformer.apply(it) } - override fun map(composer: UComposer): UInputFieldId = - this + override fun map(composer: UComposer): UInputFieldId { + check(contextHeap == null) { "ContextHeap is not null in composition!" } + @Suppress("UNCHECKED_CAST") + return copy(contextHeap = composer.heapEvaluator.toMutableHeap() as USymbolicHeap) + } override fun accept(visitor: URegionIdVisitor): R = visitor.visit(this) @@ -81,7 +96,8 @@ data class UInputFieldId internal constructor( } } -interface UArrayId : URegionId { +interface UArrayId> : + URegionId { val arrayType: ArrayType } @@ -94,7 +110,17 @@ data class UAllocatedArrayId internal constructor( val address: UConcreteHeapAddress, override val sort: Sort, override val defaultValue: UExpr, -) : UArrayId { + val contextHeap: USymbolicHeap<*, ArrayType>?, +) : UArrayId> { + override fun instantiate( + region: USymbolicMemoryRegion, USizeExpr, Sort>, + key: USizeExpr + ): UExpr = if (contextHeap == null) { + sort.uctx.mkAllocatedArrayReading(region, key) + } else { + region.applyTo(contextHeap) + read(contextHeap, key) + } @Suppress("UNCHECKED_CAST") override fun read( @@ -116,16 +142,20 @@ data class UAllocatedArrayId internal constructor( heap.writeArrayIndex(ref, key, arrayType as ArrayType, sort, value, guard) } + override fun keyMapper( transformer: UExprTransformer, ): KeyMapper = { transformer.apply(it) } + override fun map(composer: UComposer): UAllocatedArrayId { val composedDefaultValue = composer.compose(defaultValue) - if (composedDefaultValue === defaultValue) { - return this - } - return copy(defaultValue = composedDefaultValue) + check(contextHeap == null) { "ContextHeap is not null in composition!" } + @Suppress("UNCHECKED_CAST") + return copy( + contextHeap = composer.heapEvaluator.toMutableHeap() as USymbolicHeap<*, ArrayType>, + defaultValue = composedDefaultValue + ) } // we don't include arrayType into hashcode and equals, because [address] already defines unambiguously @@ -158,8 +188,18 @@ data class UAllocatedArrayId internal constructor( data class UInputArrayId internal constructor( override val arrayType: ArrayType, override val sort: Sort, -) : UArrayId { + val contextHeap: USymbolicHeap<*, ArrayType>?, +) : UArrayId> { override val defaultValue: UExpr? get() = null + override fun instantiate( + region: USymbolicMemoryRegion, USymbolicArrayIndex, Sort>, + key: USymbolicArrayIndex + ): UExpr = if (contextHeap == null) { + sort.uctx.mkInputArrayReading(region, key.first, key.second) + } else { + region.applyTo(contextHeap) + read(contextHeap, key) + } @Suppress("UNCHECKED_CAST") override fun read( @@ -186,9 +226,11 @@ data class UInputArrayId internal constructor( override fun accept(visitor: URegionIdVisitor): R = visitor.visit(this) - override fun map(composer: UComposer): UInputArrayId = - this - + override fun map(composer: UComposer): UInputArrayId { + check(contextHeap == null) { "ContextHeap is not null in composition!" } + @Suppress("UNCHECKED_CAST") + return copy(contextHeap = composer.heapEvaluator.toMutableHeap() as USymbolicHeap<*, ArrayType>) + } override fun toString(): String { return "inputArray($arrayType)" } @@ -200,8 +242,18 @@ data class UInputArrayId internal constructor( data class UInputArrayLengthId internal constructor( val arrayType: ArrayType, override val sort: USizeSort, -) : URegionId { + val contextHeap: USymbolicHeap<*, ArrayType>?, +) : URegionId> { override val defaultValue: UExpr? get() = null + override fun instantiate( + region: USymbolicMemoryRegion, UHeapRef, USizeSort>, + key: UHeapRef + ): UExpr = if (contextHeap == null) { + sort.uctx.mkInputArrayLengthReading(region, key) + } else { + region.applyTo(contextHeap) + read(contextHeap, key) + } @Suppress("UNCHECKED_CAST") override fun read( @@ -224,9 +276,11 @@ data class UInputArrayLengthId internal constructor( transformer: UExprTransformer, ): KeyMapper = { transformer.apply(it) } - override fun map(composer: UComposer): UInputArrayLengthId = - this - + override fun map(composer: UComposer): UInputArrayLengthId { + check(contextHeap == null) { "ContextHeap is not null in composition!" } + @Suppress("UNCHECKED_CAST") + return copy(contextHeap = composer.heapEvaluator.toMutableHeap() as USymbolicHeap<*, ArrayType>) + } override fun accept(visitor: URegionIdVisitor): R = visitor.visit(this) diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt index c3369c08e3..10666cacd8 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt @@ -2,22 +2,23 @@ package org.usvm import org.ksmt.sort.KArray2Sort import org.ksmt.sort.KArraySort +import org.ksmt.utils.cast import java.util.IdentityHashMap /** * [URegionTranslator] defines a template method that translates a region reading to a specific [UExpr] with a sort [Sort]. */ -class URegionTranslator, Key, Sort : USort, Result>( +class URegionTranslator, Key, Sort : USort, Result>( private val updateTranslator: UMemoryUpdatesVisitor, ) { - fun translateReading(region: UMemoryRegion, key: Key): UExpr { + fun translateReading(region: USymbolicMemoryRegion, key: Key): UExpr { val translated = translate(region) return updateTranslator.visitSelect(translated, key) } private val visitorCache = IdentityHashMap() - private fun translate(region: UMemoryRegion): Result = + private fun translate(region: USymbolicMemoryRegion): Result = region.updates.accept(updateTranslator, visitorCache) } @@ -52,16 +53,18 @@ internal class U1DArrayUpdateTranslate( // previous.store(update.key, mkIte(update.guard, update.value, previous.select(update.key))) } - is URangedUpdateNode<*, *, *, *, *> -> { + is URangedUpdateNode<*, *, *, *> -> { when (update.guard) { falseExpr -> previous else -> { @Suppress("UNCHECKED_CAST") - (update as URangedUpdateNode, *, Any?, UExpr, Sort>) + (update as URangedUpdateNode, Any?, UExpr, Sort>) val key = mkFreshConst("k", previous.sort.domain) val from = update.region - val convertedKey = from.regionId.keyMapper(exprTranslator)(update.keyConverter.convert(key)) + + val keyMapper = from.regionId.keyMapper(exprTranslator) + val convertedKey = keyMapper(update.keyConverter.convert(key)) val isInside = update.includesSymbolically(key).translated // already includes guard val result = exprTranslator.translateRegionReading(from, convertedKey) val ite = mkIte(isInside, result, previous.select(key)) @@ -107,18 +110,18 @@ internal class U2DArrayUpdateVisitor< mkIte(guard, previous.store(key1, key2, value), previous) } - is URangedUpdateNode<*, *, *, *, *> -> { + is URangedUpdateNode<*, *, *, *> -> { when (update.guard) { falseExpr -> previous else -> { @Suppress("UNCHECKED_CAST") - (update as URangedUpdateNode, *, Any?, Pair, UExpr>, Sort>) + (update as URangedUpdateNode, Any?, Pair, UExpr>, Sort>) val key1 = mkFreshConst("k1", previous.sort.domain0) val key2 = mkFreshConst("k2", previous.sort.domain1) val region = update.region - val convertedKey = - region.regionId.keyMapper(exprTranslator)(update.keyConverter.convert(key1 to key2)) + val keyMapper = region.regionId.keyMapper(exprTranslator) + val convertedKey = keyMapper(update.keyConverter.convert(key1 to key2)) val isInside = update.includesSymbolically(key1 to key2).translated // already includes guard val result = exprTranslator.translateRegionReading(region, convertedKey) val ite = mkIte(isInside, result, previous.select(key1, key2)) diff --git a/usvm-core/src/main/kotlin/org/usvm/RegistersStack.kt b/usvm-core/src/main/kotlin/org/usvm/RegistersStack.kt index d8847e234e..0b3f326cde 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegistersStack.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegistersStack.kt @@ -1,7 +1,6 @@ package org.usvm import org.ksmt.expr.KExpr -import org.ksmt.solver.KModel import org.ksmt.utils.asExpr import java.util.Stack diff --git a/usvm-core/src/main/kotlin/org/usvm/UExprTransformer.kt b/usvm-core/src/main/kotlin/org/usvm/UExprTransformer.kt index 9d9d54c78a..8a2692c3de 100644 --- a/usvm-core/src/main/kotlin/org/usvm/UExprTransformer.kt +++ b/usvm-core/src/main/kotlin/org/usvm/UExprTransformer.kt @@ -18,7 +18,7 @@ abstract class UExprTransformer(ctx: UContext): KNonRecursiveTransf abstract fun transform(expr: UIsExpr): UBoolExpr - abstract fun transform(expr: UNullRef): UExpr - abstract fun transform(expr: UConcreteHeapRef): UExpr + + abstract fun transform(expr: UNullRef): UExpr } \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt b/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt index 80bd810185..a38b843b79 100644 --- a/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt +++ b/usvm-core/src/main/kotlin/org/usvm/UpdateNodes.kt @@ -62,8 +62,7 @@ sealed interface UUpdateNode { */ fun map( keyMapper: KeyMapper, - composer: UComposer, - instantiatorFactory: UInstantiatorFactory + composer: UComposer ): UUpdateNode } @@ -113,8 +112,7 @@ class UPinpointUpdateNode( override fun map( keyMapper: KeyMapper, - composer: UComposer, - instantiatorFactory: UInstantiatorFactory + composer: UComposer ): UPinpointUpdateNode { val mappedKey = keyMapper(key) val mappedValue = composer.compose(value) @@ -199,10 +197,10 @@ sealed class UMemoryKeyConverter( * with values from memory region [region] read from range * of addresses [[keyConverter].convert([fromKey]) : [keyConverter].convert([toKey])] */ -class URangedUpdateNode, ArrayType, SrcKey, DstKey, Sort : USort>( +class URangedUpdateNode, SrcKey, DstKey, Sort : USort>( val fromKey: DstKey, val toKey: DstKey, - val region: UMemoryRegion, + val region: USymbolicMemoryRegion, private val concreteComparer: (DstKey, DstKey) -> Boolean, private val symbolicComparer: (DstKey, DstKey) -> UBoolExpr, val keyConverter: UMemoryKeyConverter, @@ -226,15 +224,13 @@ class URangedUpdateNode, ArrayType, override fun value(key: DstKey): UExpr = region.read(keyConverter.convert(key)) - @Suppress("UNCHECKED_CAST") override fun map( keyMapper: KeyMapper, - composer: UComposer, - instantiatorFactory: UInstantiatorFactory - ): URangedUpdateNode { + composer: UComposer + ): URangedUpdateNode { val mappedFromKey = keyMapper(fromKey) val mappedToKey = keyMapper(toKey) - val mappedRegion = region.map(composer, instantiatorFactory) + val mappedRegion = region.map(composer) val mappedKeyConverter = keyConverter.map(composer) val mappedGuard = composer.compose(guard) @@ -311,7 +307,7 @@ class URangedUpdateNode, ArrayType, // Ignores update override fun equals(other: Any?): Boolean = - other is URangedUpdateNode<*, *, *, *, *> && + other is URangedUpdateNode<*, *, *, *> && this.fromKey == other.fromKey && this.toKey == other.toKey && this.guard == other.guard @@ -380,11 +376,11 @@ class UInputToAllocatedKeyConverter( * Used when copying data from input array to another input array. */ class UInputToInputKeyConverter( - srcSymbolicArrayIndex: USymbolicArrayIndex, + srcFromSymbolicArrayIndex: USymbolicArrayIndex, dstFromSymbolicArrayIndex: USymbolicArrayIndex, dstToIndex: USizeExpr ) : UMemoryKeyConverter( - srcSymbolicArrayIndex, + srcFromSymbolicArrayIndex, dstFromSymbolicArrayIndex, dstToIndex ) { diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index 48fbbea2ca..9de7d6f4fb 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -2,6 +2,7 @@ package org.usvm import io.mockk.every import io.mockk.mockk +import kotlinx.collections.immutable.persistentMapOf import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -11,12 +12,14 @@ import org.ksmt.expr.KBitVec32Value import org.ksmt.expr.KExpr import org.ksmt.expr.printer.ExpressionPrinter import org.ksmt.expr.transformer.KTransformerBase -import org.ksmt.solver.model.DefaultValueSampler.Companion.sampleValue import org.ksmt.sort.KBv32Sort +import org.ksmt.utils.sampleValue import org.usvm.UAddressCounter.Companion.NULL_ADDRESS import kotlin.reflect.KClass import kotlin.test.assertEquals +import kotlin.test.assertIs import kotlin.test.assertSame +import kotlin.test.assertTrue internal class CompositionTest { private lateinit var stackEvaluator: URegistersStackEvaluator @@ -25,11 +28,13 @@ internal class CompositionTest { private lateinit var mockEvaluator: UMockEvaluator private lateinit var ctx: UContext + private lateinit var concreteNull: UConcreteHeapRef private lateinit var composer: UComposer @BeforeEach fun initializeContext() { ctx = UContext() + concreteNull = ctx.mkConcreteHeapRef(NULL_ADDRESS) stackEvaluator = mockk() heapEvaluator = mockk() typeEvaluator = mockk() @@ -201,11 +206,10 @@ internal class CompositionTest { ).write(fstAddress, fstResultValue, guard = trueExpr) .write(sndAddress, sndResultValue, guard = trueExpr) - val regionId = UInputArrayLengthId(arrayType, bv32Sort) + val regionId = UInputArrayLengthId(arrayType, bv32Sort, contextHeap = null) val regionArray = UInputArrayLengthRegion( regionId, updates, - instantiator = { key, region -> mkInputArrayLengthReading(region, key) } ) val fstConcreteAddress = mkConcreteHeapRef(firstAddress) @@ -256,9 +260,8 @@ internal class CompositionTest { val arrayType: KClass> = Array::class val region = UInputArrayRegion( - UInputArrayId(arrayType, bv32Sort), + UInputArrayId(arrayType, bv32Sort, contextHeap = null), updates, - instantiator = { key, memoryRegion -> mkInputArrayReading(memoryRegion, key.first, key.second) } ) // TODO replace with jacoDB type @@ -360,11 +363,10 @@ internal class CompositionTest { ).write(fstIndex, 1.toBv(), guard = trueExpr) .write(sndIndex, 2.toBv(), guard = trueExpr) - val regionId = UAllocatedArrayId(arrayType, address, bv32Sort, bv32Sort.sampleValue()) + val regionId = UAllocatedArrayId(arrayType, address, bv32Sort, bv32Sort.sampleValue(), contextHeap = null) val regionArray = UAllocatedArrayRegion( regionId, updates, - instantiator = { key, region -> mkAllocatedArrayReading(region, key) } ) val firstReading = mkAllocatedArrayReading(regionArray, fstSymbolicIndex) @@ -415,9 +417,8 @@ internal class CompositionTest { // An empty region with one write in it val region = UInputFieldRegion( - UInputFieldId(field, bv32Sort), + UInputFieldId(field, bv32Sort, contextHeap = null), updates, - instantiator = { key, region -> mkInputFieldReading(region, key) } ).write(aAddress, 43.toBv(), guard = trueExpr) every { aAddress.accept(any()) } returns aAddress @@ -450,7 +451,9 @@ internal class CompositionTest { @Test fun testHeapRefEq() = with(ctx) { - val stackModel = URegistersStackModel(mapOf(0 to mkConcreteHeapRef(-1), 1 to mkConcreteHeapRef(-2))) + val concreteNull = ctx.mkConcreteHeapRef(NULL_ADDRESS) + val stackModel = + URegistersStackModel(concreteNull, mapOf(0 to mkConcreteHeapRef(-1), 1 to mkConcreteHeapRef(-2))) val composer = UComposer(this, stackModel, mockk(), mockk(), mockk()) @@ -462,7 +465,8 @@ internal class CompositionTest { @Test fun testHeapRefNullAddress() = with(ctx) { - val stackModel = URegistersStackModel(mapOf(0 to mkConcreteHeapRef(0))) + val concreteNull = ctx.mkConcreteHeapRef(NULL_ADDRESS) + val stackModel = URegistersStackModel(concreteNull, mapOf(0 to mkConcreteHeapRef(0))) val heapEvaluator: UReadOnlySymbolicHeap = mockk() every { heapEvaluator.nullRef() } returns mkConcreteHeapRef(NULL_ADDRESS) @@ -477,11 +481,14 @@ internal class CompositionTest { @Test fun testComposeAllocatedArray() = with(ctx) { - val heapEvaluator: UReadOnlySymbolicHeap = mockk() - - every { heapEvaluator.nullRef() } returns ctx.mkConcreteHeapRef(NULL_ADDRESS) + val heapEvaluator = UHeapModel( + concreteNull, + persistentMapOf(), + persistentMapOf(), + persistentMapOf(), + ) - val stackModel = URegistersStackModel(mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) + val stackModel = URegistersStackModel(concreteNull, mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) @@ -499,18 +506,26 @@ internal class CompositionTest { @Test fun testComposeRangedUpdate() = with(ctx) { - val heapEvaluator: USymbolicHeap = mockk() - every { heapEvaluator.nullRef() } returns ctx.mkConcreteHeapRef(NULL_ADDRESS) + val arrayType = mockk() + val heapEvaluator = UHeapModel( + concreteNull, + persistentMapOf(), + persistentMapOf(), + persistentMapOf(), + ) + val composedSymbolicHeapRef = ctx.mkConcreteHeapRef(-1) - val stackModel = URegistersStackModel(mapOf(0 to composedSymbolicHeapRef, 1 to ctx.mkBv(0))) + heapEvaluator.writeArrayIndex(composedSymbolicHeapRef, mkBv(0), arrayType, bv32Sort, mkBv(1), trueExpr) + + val stackModel = + URegistersStackModel(concreteNull, mapOf(0 to composedSymbolicHeapRef, 1 to ctx.mkBv(0))) val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) val symbolicRef = mkRegisterReading(0, addressSort) - val arrayType = mockk() val fromRegion = emptyInputArrayRegion(arrayType, bv32Sort) - val concreteRef = mkConcreteHeapRef(0) + val concreteRef = mkConcreteHeapRef(1) val keyConverter = UInputToAllocatedKeyConverter(symbolicRef to mkBv(0), concreteRef to mkBv(0), mkBv(5)) val concreteRegion = emptyAllocatedArrayRegion(arrayType, concreteRef.address, bv32Sort) @@ -520,22 +535,77 @@ internal class CompositionTest { val reading = concreteRegion.read(idx) - every { heapEvaluator.toMutableHeap() } returns heapEvaluator - every { heapEvaluator.readArrayIndex(composedSymbolicHeapRef, ctx.mkBv(0), arrayType, bv32Sort) } returns mkBv(1) - val expr = composer.compose(reading) assertSame(mkBv(1), expr) } @Test - fun testNullRefRegionDefaultValue() = with(ctx) { - val concreteNull = ctx.mkConcreteHeapRef(NULL_ADDRESS) + fun testComposeComplexRangedUpdate() = with(ctx) { + val arrayType = mockk() + val heapEvaluator = URegionHeap(ctx) + + val symbolicRef0 = mkRegisterReading(0, addressSort) + val symbolicRef1 = mkRegisterReading(1, addressSort) + val symbolicRef2 = mkRegisterReading(2, addressSort) + val composedSymbolicHeapRef = ctx.mkConcreteHeapRef(1) + + heapEvaluator.writeArrayIndex(composedSymbolicHeapRef, mkBv(3), arrayType, bv32Sort, mkBv(1337), trueExpr) + + val stackModel = URegistersStackModel( + concreteNull, mapOf( + 0 to composedSymbolicHeapRef, + 1 to composedSymbolicHeapRef, + 2 to composedSymbolicHeapRef, + 3 to ctx.mkRegisterReading(3, bv32Sort), + ) + ) + val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + + val fromRegion0 = emptyInputArrayRegion(arrayType, bv32Sort) + .write(symbolicRef0 to mkBv(0), mkBv(42), trueExpr) + + val keyConverter1 = UInputToInputKeyConverter(symbolicRef0 to mkBv(0), symbolicRef1 to mkBv(0), mkBv(5)) + + val fromRegion1 = fromRegion0 + .copyRange(fromRegion0, symbolicRef0 to mkBv(0), symbolicRef0 to mkBv(5), keyConverter1, trueExpr) + + val keyConverter2 = UInputToInputKeyConverter(symbolicRef1 to mkBv(0), symbolicRef2 to mkBv(0), mkBv(5)) + + val fromRegion2 = fromRegion1 + .copyRange(fromRegion1, symbolicRef1 to mkBv(0), symbolicRef1 to mkBv(5), keyConverter2, trueExpr) + + val idx0 = mkRegisterReading(3, bv32Sort) + + val reading0 = fromRegion2.read(symbolicRef2 to idx0) + + val composedExpr0 = composer.compose(reading0) + val composedReading0 = assertIs>(composedExpr0) + + fun UMemoryUpdates<*, *>.allUpdates(): Collection> = + fold(mutableListOf()) { acc, r -> + acc += r + acc += (r as? URangedUpdateNode<*, *, *, *>)?.region?.updates?.allUpdates() ?: emptyList() + acc + } + + val pinpointUpdates = composedReading0 + .region + .updates + .allUpdates() + .filterIsInstance>() + + assertTrue { pinpointUpdates.any { it.key == mkBv(3) && it.value == mkBv(1337) } } + assertTrue { pinpointUpdates.any { it.key == mkBv(0) && it.value == mkBv(42) } } + } + + @Test + fun testNullRefRegionDefaultValue() = with(ctx) { val heapEvaluator: UReadOnlySymbolicHeap = mockk() every { heapEvaluator.nullRef() } returns concreteNull - val stackModel = URegistersStackModel(mapOf(0 to mkBv(0), 1 to mkBv(0), 2 to mkBv(2))) + val stackModel = URegistersStackModel(concreteNull, mapOf(0 to mkBv(0), 1 to mkBv(0), 2 to mkBv(2))) val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) diff --git a/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt index 6f264e13f8..76c24da7d5 100644 --- a/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt @@ -55,7 +55,7 @@ class MapCompositionTest { every { composer.compose(value) } returns 1.toBv() every { composer.compose(mkTrue()) } returns mkTrue() - val composedUpdates = updatesToCompose.map(keyMapper = { composer.compose(it) }, composer, mockk()) + val composedUpdates = updatesToCompose.map(keyMapper = { composer.compose(it) }, composer) assert(composedUpdates.isEmpty()) } @@ -97,7 +97,7 @@ class MapCompositionTest { every { composer.compose(mkTrue()) } returns mkTrue() // ComposedUpdates contains only one update in a region {3} - val composedUpdates = updatesToCompose.map(keyMapper = { composer.compose(it) }, composer, mockk()) + val composedUpdates = updatesToCompose.map(keyMapper = { composer.compose(it) }, composer) assertFalse(composedUpdates.isEmpty()) @@ -124,7 +124,7 @@ class MapCompositionTest { every { composer.compose(value) } returns value every { composer.compose(guard) } returns guard - val mappedNode = updateNode.map({ k -> composer.compose(k) }, composer, mockk()) + val mappedNode = updateNode.map({ k -> composer.compose(k) }, composer) assertSame(expected = updateNode, actual = mappedNode) } @@ -143,7 +143,7 @@ class MapCompositionTest { every { composer.compose(value) } returns 1.toBv() every { composer.compose(guard) } returns mkTrue() - val mappedNode = updateNode.map({ k -> composer.compose(k) }, composer, mockk()) + val mappedNode = updateNode.map({ k -> composer.compose(k) }, composer) assertNotSame(illegal = updateNode, actual = mappedNode) assertSame(expected = composedKey, actual = mappedNode.key) @@ -156,7 +156,7 @@ class MapCompositionTest { val addr = addressSort.mkConst("addr") val fromKey = sizeSort.mkConst("fromKey") as UExpr val toKey = sizeSort.mkConst("toKey") as UExpr - val region = mockk, UExpr, UBv32Sort>>() + val region = mockk, UExpr, UBv32Sort>>() val guard = boolSort.mkConst("guard") val updateNode = URangedUpdateNode( @@ -176,10 +176,10 @@ class MapCompositionTest { every { composer.compose(addr) } returns addr every { composer.compose(fromKey) } returns fromKey every { composer.compose(toKey) } returns toKey - every { region.map(composer, any()) } returns region + every { region.map(composer) } returns region every { composer.compose(guard) } returns guard - val mappedUpdateNode = updateNode.map({ k -> composer.compose((k)) }, composer, mockk()) + val mappedUpdateNode = updateNode.map({ k -> composer.compose((k)) }, composer) assertSame(expected = updateNode, actual = mappedUpdateNode) } @@ -189,7 +189,7 @@ class MapCompositionTest { val addr = mkConcreteHeapRef(0) val fromKey = sizeSort.mkConst("fromKey") val toKey = sizeSort.mkConst("toKey") - val region = mockk, USizeExpr, UBv32Sort>>() + val region = mockk, USizeExpr, UBv32Sort>>() val guard = boolSort.mkConst("guard") val updateNode = URangedUpdateNode( @@ -208,16 +208,16 @@ class MapCompositionTest { val composedFromKey = sizeSort.mkConst("composedFromKey") val composedToKey = sizeSort.mkConst("composedToKey") - val composedRegion = mockk, UExpr, UBv32Sort>>() + val composedRegion = mockk, UExpr, UBv32Sort>>() val composedGuard = mkTrue() every { composer.compose(addr) } returns addr every { composer.compose(fromKey) } returns composedFromKey every { composer.compose(toKey) } returns composedToKey - every { region.map(composer, any()) } returns composedRegion + every { region.map(composer) } returns composedRegion every { composer.compose(guard) } returns composedGuard - val mappedUpdateNode = updateNode.map({ k -> composer.compose((k)) }, composer, mockk()) + val mappedUpdateNode = updateNode.map({ k -> composer.compose((k)) }, composer) assertNotSame(illegal = updateNode, actual = mappedUpdateNode) assertSame(expected = composedFromKey, actual = mappedUpdateNode.fromKey) @@ -234,7 +234,7 @@ class MapCompositionTest { { _, _ -> shouldNotBeCalled() } ) - val mappedUpdates = emptyUpdates.map({ k -> composer.compose(k) }, composer, mockk()) + val mappedUpdates = emptyUpdates.map({ k -> composer.compose(k) }, composer) assertSame(expected = emptyUpdates, actual = mappedUpdates) } @@ -259,7 +259,7 @@ class MapCompositionTest { every { composer.compose(sndValue) } returns sndValue every { composer.compose(mkTrue()) } returns mkTrue() - val mappedUpdates = flatUpdates.map({ k -> composer.compose(k) }, composer, mockk()) + val mappedUpdates = flatUpdates.map({ k -> composer.compose(k) }, composer) assertSame(expected = flatUpdates, actual = mappedUpdates) } @@ -289,7 +289,7 @@ class MapCompositionTest { every { composer.compose(sndValue) } returns composedSndValue every { composer.compose(mkTrue()) } returns mkTrue() - val mappedUpdates = flatUpdates.map({ k -> composer.compose(k) }, composer, mockk()) + val mappedUpdates = flatUpdates.map({ k -> composer.compose(k) }, composer) assertNotSame(illegal = flatUpdates, actual = mappedUpdates) @@ -327,7 +327,7 @@ class MapCompositionTest { every { composer.compose(sndValue) } returns sndValue every { composer.compose(mkTrue()) } returns mkTrue() - val mappedUpdates = treeUpdates.map({ k -> composer.compose(k) }, composer, mockk()) + val mappedUpdates = treeUpdates.map({ k -> composer.compose(k) }, composer) assertSame(expected = treeUpdates, actual = mappedUpdates) } @@ -360,7 +360,7 @@ class MapCompositionTest { every { composer.compose(sndValue) } returns composedSndValue every { composer.compose(mkTrue()) } returns mkTrue() - val mappedUpdates = treeUpdates.map({ k -> composer.compose(k) }, composer, mockk()) + val mappedUpdates = treeUpdates.map({ k -> composer.compose(k) }, composer) assertNotSame(illegal = treeUpdates, actual = mappedUpdates) diff --git a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt index b56b9f17cf..a724e91045 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test import org.ksmt.solver.KSolverStatus import org.ksmt.solver.z3.KZ3Solver import org.ksmt.utils.mkConst -import org.usvm.UContext.Companion.sampleValue +import org.ksmt.utils.sampleValue import kotlin.test.assertSame class TranslationTest { From ffee654eee457cf05197c4da569c05812b067100 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Mon, 17 Apr 2023 21:01:47 +0300 Subject: [PATCH 22/28] Refactor: minor --- .../main/kotlin/org/usvm/ExprTranslator.kt | 6 +- usvm-core/src/main/kotlin/org/usvm/Heap.kt | 39 +---- ...efSplittingUtil.kt => HeapRefSplitting.kt} | 0 usvm-core/src/main/kotlin/org/usvm/Memory.kt | 2 +- .../src/main/kotlin/org/usvm/MemoryRegions.kt | 6 +- .../src/main/kotlin/org/usvm/MemoryUpdates.kt | 6 +- .../src/main/kotlin/org/usvm/ModelDecoder.kt | 79 ++------- .../src/main/kotlin/org/usvm/ModelRegions.kt | 4 +- .../src/main/kotlin/org/usvm/PathCondition.kt | 27 +-- .../src/main/kotlin/org/usvm/RegionIds.kt | 11 +- .../test/kotlin/org/usvm/CompositionTest.kt | 67 +------- .../kotlin/org/usvm/ModelCompositionTest.kt | 162 ++++++++++++++++++ .../test/kotlin/org/usvm/ModelDecodingTest.kt | 1 + .../test/kotlin/org/usvm/TranslationTest.kt | 2 +- 14 files changed, 222 insertions(+), 190 deletions(-) rename usvm-core/src/main/kotlin/org/usvm/{HeapRefSplittingUtil.kt => HeapRefSplitting.kt} (100%) create mode 100644 usvm-core/src/test/kotlin/org/usvm/ModelCompositionTest.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index 6900cf10bd..d0e1c37234 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -106,7 +106,7 @@ open class UExprTranslator constructor( return URegionTranslator(updateTranslator) } - val regionIdInitialValueProvider = URegionIdInitialValueProviderBase(onDefaultValuePresent = { translate(it) }) + val regionIdInitialValueProvider = URegionIdInitialValueProvider(onDefaultValuePresent = { translate(it) }) } open class UCachingExprTranslator( @@ -145,13 +145,13 @@ interface URegionIdTranslatorFactory : URegionIdVisitor, ): URegionTranslator, Key, Sort, *> { @Suppress("UNCHECKED_CAST") - return apply(regionId) as URegionTranslator, Key, Sort, *> + return regionId.accept(this) as URegionTranslator, Key, Sort, *> } } typealias URegionIdInitialValueFactory = URegionIdVisitor> -class URegionIdInitialValueProviderBase( +open class URegionIdInitialValueProvider( val onDefaultValuePresent: (UExpr<*>) -> UExpr<*>, ) : URegionIdVisitor>> { override fun visit(regionId: UInputFieldId): UExpr> { diff --git a/usvm-core/src/main/kotlin/org/usvm/Heap.kt b/usvm-core/src/main/kotlin/org/usvm/Heap.kt index 1b54b81f59..86ad622b31 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Heap.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Heap.kt @@ -20,26 +20,6 @@ interface UReadOnlyHeap { typealias UReadOnlySymbolicHeap = UReadOnlyHeap, USizeExpr, Field, ArrayType, UBoolExpr> -//class UEmptyHeap(private val ctx: UContext) : UReadOnlySymbolicHeap { -// override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr = -// sort.sampleValue() -// -// override fun readArrayIndex( -// ref: UHeapRef, -// index: USizeExpr, -// arrayType: ArrayType, -// elementSort: Sort, -// ): UExpr = elementSort.sampleValue() -// -// override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType) = -// ctx.zeroSize -// -// override fun toMutableHeap(): UHeap, USizeExpr, Field, ArrayType, UBoolExpr> = -// URegionHeap(ctx) -// -// override fun nullRef(): UHeapRef = ctx.nullRef -//} - interface UHeap : UReadOnlyHeap { fun writeField(ref: Ref, field: Field, sort: Sort, value: Value, guard: Guard) @@ -68,8 +48,6 @@ interface UHeap : fun allocate(): UConcreteHeapAddress fun allocateArray(count: SizeT): UConcreteHeapAddress - - fun clone(): UHeap } typealias USymbolicHeap = UHeap, USizeExpr, Field, ArrayType, UBoolExpr> @@ -131,7 +109,7 @@ data class URegionHeap( arrayType: ArrayType, ): UInputArrayLengthRegion = inputLengths[arrayType] - ?: emptyArrayLengthRegion(arrayType, ctx.sizeSort) + ?: emptyInputArrayLengthRegion(arrayType, ctx.sizeSort) @Suppress("UNCHECKED_CAST", "UNUSED") fun , Key, Sort : USort> getRegion(region: RegionId): USymbolicMemoryRegion = @@ -332,17 +310,14 @@ data class URegionHeap( return address } - override fun clone(): URegionHeap = - URegionHeap( - ctx, lastAddress, - allocatedFields, inputFields, - allocatedArrays, inputArrays, - allocatedLengths, inputLengths - ) - override fun nullRef(): UHeapRef = ctx.nullRef - override fun toMutableHeap() = clone() + override fun toMutableHeap() = URegionHeap( + ctx, lastAddress, + allocatedFields, inputFields, + allocatedArrays, inputArrays, + allocatedLengths, inputLengths + ) } @Suppress("UNCHECKED_CAST") diff --git a/usvm-core/src/main/kotlin/org/usvm/HeapRefSplittingUtil.kt b/usvm-core/src/main/kotlin/org/usvm/HeapRefSplitting.kt similarity index 100% rename from usvm-core/src/main/kotlin/org/usvm/HeapRefSplittingUtil.kt rename to usvm-core/src/main/kotlin/org/usvm/HeapRefSplitting.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/Memory.kt b/usvm-core/src/main/kotlin/org/usvm/Memory.kt index 8e95d89322..cf91f1ba62 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Memory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Memory.kt @@ -121,5 +121,5 @@ open class UMemoryBase( } override fun clone(): UMemoryBase = - UMemoryBase(ctx, typeSystem, stack.clone(), heap.clone(), types, mocker) + UMemoryBase(ctx, typeSystem, stack.clone(), heap.toMutableHeap(), types, mocker) } diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt index 1fa4a3ddb5..b32ef5fbca 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt @@ -146,11 +146,9 @@ data class USymbolicMemoryRegion, // non-null reference as default value, or implement splitting by default value. assert(defaultValue == null || !predicate(defaultValue)) - val count = matchingWrites.size val splitUpdates = updates.read(key).split(key, predicate, matchingWrites, guardBuilder) - val sizeRemainedUnchanged = matchingWrites.size == count - return if (sizeRemainedUnchanged) { + return if (splitUpdates === updates) { this } else { this.copy(updates = splitUpdates) @@ -364,7 +362,7 @@ fun emptyInputArrayRegion( return USymbolicMemoryRegion(UInputArrayId(arrayType, sort, contextHeap = null), updates) } -fun emptyArrayLengthRegion( +fun emptyInputArrayLengthRegion( arrayType: ArrayType, sizeSort: USizeSort, ): UInputArrayLengthRegion = diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt index a140fe8f65..5a0706cbb7 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt @@ -105,7 +105,11 @@ class UFlatUpdates private constructor( val next: UFlatUpdates, ) - override fun read(key: Key): UMemoryUpdates = this + override fun read(key: Key): UFlatUpdates = + when { + node != null && node.update.includesSymbolically(key).isFalse -> node.next.read(key) + else -> this + } override fun write(key: Key, value: UExpr, guard: UBoolExpr): UFlatUpdates = UFlatUpdates( diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt index 3e2d8bd476..e79d243f17 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt @@ -99,7 +99,6 @@ open class UModelDecoderBase( model: KModel, addressesMapping: AddressesMapping, ): UHeapModel { - val resolvedInputFields = mutableMapOf>() val resolvedInputArrays = mutableMapOf>() val resolvedInputArrayLengths = mutableMapOf>() @@ -137,9 +136,9 @@ open class UModelDecoderBase( return UHeapModel( addressesMapping.getValue(translatedNullRef), - resolvedInputFields.toPersistentMap(), - resolvedInputArrays.toPersistentMap(), - resolvedInputArrayLengths.toPersistentMap() + resolvedInputFields, + resolvedInputArrays, + resolvedInputArrayLengths ) } @@ -196,6 +195,7 @@ class UIndexedMockModel( require(symbol is UIndexedMethodReturnValue<*, Sort>) val sort = symbol.sort + @Suppress("UNCHECKED_CAST") val key = symbol.method as Method to symbol.callIndex @@ -205,9 +205,9 @@ class UIndexedMockModel( class UHeapModel( private val nullRef: UConcreteHeapRef, - private var resolvedInputFields: PersistentMap>, - private var resolvedInputArrays: PersistentMap>, - private var resolvedInputLengths: PersistentMap>, + private val resolvedInputFields: Map>, + private val resolvedInputArrays: Map>, + private val resolvedInputLengths: Map>, ) : USymbolicHeap { @Suppress("UNCHECKED_CAST") @@ -217,7 +217,10 @@ class UHeapModel( } as UMemoryRegion @Suppress("UNCHECKED_CAST") - private fun inputArrayRegion(arrayType: ArrayType, sort: Sort): UMemoryRegion = + private fun inputArrayRegion( + arrayType: ArrayType, + sort: Sort + ): UMemoryRegion = resolvedInputArrays.getOrElse(arrayType) { UMemory2DArray(sort.sampleValue().nullAddress(nullRef)) } as UMemoryRegion @@ -274,24 +277,7 @@ class UHeapModel( sort: Sort, value: UExpr, guard: UBoolExpr, - ) { - // Since all values in the model are interpreted, we can check the exact guard value. - when { - guard.isFalse -> return - else -> require(guard.isTrue) - } - - val valueToWrite = value.asExpr(sort) - - // All the expressions in the model are interpreted, therefore, they must - // have concrete addresses. Moreover, the model known only about input values - // which have addresses less or equal than INITIAL_INPUT_ADDRESS - require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - - val region = inputFieldRegion(field, sort).write(ref, valueToWrite, guard) - resolvedInputFields = resolvedInputFields.put(field, region) - - } + ) = error("Illegal operation for a model") override fun writeArrayIndex( ref: UHeapRef, @@ -300,35 +286,10 @@ class UHeapModel( sort: Sort, value: UExpr, guard: UBoolExpr, - ) { - // Since all values in the model are interpreted, we can check the exact guard value. - when { - guard.isFalse -> return - else -> require(guard.isTrue) - } - - // All the expressions in the model are interpreted, therefore, they must - // have concrete addresses. Moreover, the model known only about input values - // which have addresses less or equal than INITIAL_INPUT_ADDRESS - require(index is KInterpretedValue) - require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) + ) = error("Illegal operation for a model") - val valueToWrite = value.asExpr(sort) - - val region = inputArrayRegion(type, sort).write(ref to index, valueToWrite, guard) - resolvedInputArrays = resolvedInputArrays.put(type, region) - } - - override fun writeArrayLength(ref: UHeapRef, size: USizeExpr, arrayType: ArrayType) { - // All the expressions in the model are interpreted, therefore, they must - // have concrete addresses. Moreover, the model known only about input values - // which have addresses less or equal than INITIAL_INPUT_ADDRESS - require(size is KInterpretedValue) - require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - - val region = inputLengthRegion(arrayType, size.sort).write(ref, size, size.sort.uctx.trueExpr) - resolvedInputLengths = resolvedInputLengths.put(arrayType, region) - } + override fun writeArrayLength(ref: UHeapRef, size: USizeExpr, arrayType: ArrayType) = + error("Illegal operation for a model") override fun memcpy( srcRef: UHeapRef, @@ -352,17 +313,9 @@ class UHeapModel( override fun allocateArray(count: USizeExpr): UConcreteHeapAddress = error("Illegal operation for a model") - override fun clone(): UHeapModel = - UHeapModel( - nullRef, - resolvedInputFields, - resolvedInputArrays, - resolvedInputLengths, - ) - override fun nullRef(): UConcreteHeapRef = nullRef - override fun toMutableHeap(): UHeapModel = clone() + override fun toMutableHeap(): UHeapModel = this } fun UExpr.nullAddress(nullRef: UConcreteHeapRef): UExpr = diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt b/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt index 11bb6c64d5..9c8692a133 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt @@ -15,7 +15,7 @@ import org.ksmt.sort.KArraySort /** * A specific evaluator for one-dimensional regions generalized by a single expression of a [KeySort]. */ -class UMemory1DArray private constructor( +class UMemory1DArray( private val values: PersistentMap, UExpr>, private val constValue: UExpr, ) : UMemoryRegion, Sort> { @@ -90,7 +90,7 @@ class UMemory1DArray private constructor( * A specific evaluator for two-dimensional regions generalized be a pair * of two expressions with [Key1Sort] and [Key2Sort] sorts. */ -class UMemory2DArray private constructor( +class UMemory2DArray( val values: PersistentMap, UExpr>, UExpr>, val constValue: UExpr, ) : UMemoryRegion, KExpr>, Sort> { diff --git a/usvm-core/src/main/kotlin/org/usvm/PathCondition.kt b/usvm-core/src/main/kotlin/org/usvm/PathCondition.kt index 582f44e572..652ecdb9cd 100644 --- a/usvm-core/src/main/kotlin/org/usvm/PathCondition.kt +++ b/usvm-core/src/main/kotlin/org/usvm/PathCondition.kt @@ -3,27 +3,30 @@ package org.usvm import kotlinx.collections.immutable.PersistentSet import kotlinx.collections.immutable.persistentSetOf -interface UPathCondition: Sequence { +interface UPathCondition : Collection { val isFalse: Boolean - fun add(constraint: UBoolExpr): UPathCondition + operator fun plus(constraint: UBoolExpr): UPathCondition } class UPathConstraintsSet( - private val constraints: PersistentSet = persistentSetOf() -) : UPathCondition -{ - fun contradiction(ctx: UContext) = - UPathConstraintsSet(persistentSetOf(ctx.mkFalse())) + private val constraints: PersistentSet = persistentSetOf(), +) : Collection by constraints, UPathCondition { + constructor(constraint: UBoolExpr) : this(persistentSetOf(constraint)) override val isFalse: Boolean get() = constraints.size == 1 && constraints.first() is UFalse - override fun add(constraint: UBoolExpr): UPathCondition { - val notConstraint = constraint.uctx.mkNot(constraint) - if (constraints.contains(notConstraint)) - return contradiction(constraint.uctx) + override operator fun plus(constraint: UBoolExpr): UPathCondition { + val ctx = constraint.uctx + val notConstraint = ctx.mkNot(constraint) + if (constraints.contains(notConstraint)) { + return contradiction(ctx) + } return UPathConstraintsSet(constraints.add(constraint)) } - override fun iterator(): Iterator = constraints.iterator() + companion object { + fun contradiction(ctx: UContext) = + UPathConstraintsSet(persistentSetOf(ctx.mkFalse())) + } } diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt index 28d9aa0b88..b735690f71 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt @@ -30,9 +30,6 @@ interface URegionId { - fun > apply(regionId: URegionId): R = - regionId.accept(this) - fun > visit(regionId: URegionId): Any? = error("You must provide visit implementation for ${regionId::class} in ${this::class}") @@ -83,7 +80,7 @@ data class UInputFieldId internal constructor( ): KeyMapper = { transformer.apply(it) } override fun map(composer: UComposer): UInputFieldId { - check(contextHeap == null) { "ContextHeap is not null in composition!" } + check(contextHeap == null) { "contextHeap is not null in composition" } @Suppress("UNCHECKED_CAST") return copy(contextHeap = composer.heapEvaluator.toMutableHeap() as USymbolicHeap) } @@ -150,7 +147,7 @@ data class UAllocatedArrayId internal constructor( override fun map(composer: UComposer): UAllocatedArrayId { val composedDefaultValue = composer.compose(defaultValue) - check(contextHeap == null) { "ContextHeap is not null in composition!" } + check(contextHeap == null) { "contextHeap is not null in composition" } @Suppress("UNCHECKED_CAST") return copy( contextHeap = composer.heapEvaluator.toMutableHeap() as USymbolicHeap<*, ArrayType>, @@ -227,7 +224,7 @@ data class UInputArrayId internal constructor( visitor.visit(this) override fun map(composer: UComposer): UInputArrayId { - check(contextHeap == null) { "ContextHeap is not null in composition!" } + check(contextHeap == null) { "contextHeap is not null in composition" } @Suppress("UNCHECKED_CAST") return copy(contextHeap = composer.heapEvaluator.toMutableHeap() as USymbolicHeap<*, ArrayType>) } @@ -277,7 +274,7 @@ data class UInputArrayLengthId internal constructor( ): KeyMapper = { transformer.apply(it) } override fun map(composer: UComposer): UInputArrayLengthId { - check(contextHeap == null) { "ContextHeap is not null in composition!" } + check(contextHeap == null) { "contextHeap is not null in composition" } @Suppress("UNCHECKED_CAST") return copy(contextHeap = composer.heapEvaluator.toMutableHeap() as USymbolicHeap<*, ArrayType>) } diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index 9de7d6f4fb..51c7c733a2 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -2,7 +2,6 @@ package org.usvm import io.mockk.every import io.mockk.mockk -import kotlinx.collections.immutable.persistentMapOf import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -479,78 +478,18 @@ internal class CompositionTest { assertSame(trueExpr, expr) } - @Test - fun testComposeAllocatedArray() = with(ctx) { - val heapEvaluator = UHeapModel( - concreteNull, - persistentMapOf(), - persistentMapOf(), - persistentMapOf(), - ) - - val stackModel = URegistersStackModel(concreteNull, mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) - - val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) - - - val region = emptyAllocatedArrayRegion(mockk(), 1, bv32Sort) - .write(0.toBv(), 0.toBv(), trueExpr) - .write(1.toBv(), 1.toBv(), trueExpr) - .write(mkRegisterReading(1, sizeSort), 2.toBv(), trueExpr) - .write(mkRegisterReading(2, sizeSort), 3.toBv(), trueExpr) - val reading = region.read(mkRegisterReading(0, sizeSort)) - - val expr = composer.compose(reading) - assertSame(mkBv(2), expr) - } - - @Test - fun testComposeRangedUpdate() = with(ctx) { - val arrayType = mockk() - val heapEvaluator = UHeapModel( - concreteNull, - persistentMapOf(), - persistentMapOf(), - persistentMapOf(), - ) - - val composedSymbolicHeapRef = ctx.mkConcreteHeapRef(-1) - heapEvaluator.writeArrayIndex(composedSymbolicHeapRef, mkBv(0), arrayType, bv32Sort, mkBv(1), trueExpr) - - val stackModel = - URegistersStackModel(concreteNull, mapOf(0 to composedSymbolicHeapRef, 1 to ctx.mkBv(0))) - val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) - - val symbolicRef = mkRegisterReading(0, addressSort) - - val fromRegion = emptyInputArrayRegion(arrayType, bv32Sort) - - val concreteRef = mkConcreteHeapRef(1) - - val keyConverter = UInputToAllocatedKeyConverter(symbolicRef to mkBv(0), concreteRef to mkBv(0), mkBv(5)) - val concreteRegion = emptyAllocatedArrayRegion(arrayType, concreteRef.address, bv32Sort) - .copyRange(fromRegion, mkBv(0), mkBv(5), keyConverter, trueExpr) - - val idx = mkRegisterReading(1, sizeSort) - - val reading = concreteRegion.read(idx) - - val expr = composer.compose(reading) - assertSame(mkBv(1), expr) - } - @Test fun testComposeComplexRangedUpdate() = with(ctx) { val arrayType = mockk() - val heapEvaluator = URegionHeap(ctx) + val regionHeap = URegionHeap(ctx) val symbolicRef0 = mkRegisterReading(0, addressSort) val symbolicRef1 = mkRegisterReading(1, addressSort) val symbolicRef2 = mkRegisterReading(2, addressSort) val composedSymbolicHeapRef = ctx.mkConcreteHeapRef(1) - heapEvaluator.writeArrayIndex(composedSymbolicHeapRef, mkBv(3), arrayType, bv32Sort, mkBv(1337), trueExpr) + regionHeap.writeArrayIndex(composedSymbolicHeapRef, mkBv(3), arrayType, bv32Sort, mkBv(1337), trueExpr) val stackModel = URegistersStackModel( concreteNull, mapOf( @@ -560,7 +499,7 @@ internal class CompositionTest { 3 to ctx.mkRegisterReading(3, bv32Sort), ) ) - val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + val composer = UComposer(this, stackModel, regionHeap, mockk(), mockk()) val fromRegion0 = emptyInputArrayRegion(arrayType, bv32Sort) .write(symbolicRef0 to mkBv(0), mkBv(42), trueExpr) diff --git a/usvm-core/src/test/kotlin/org/usvm/ModelCompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/ModelCompositionTest.kt new file mode 100644 index 0000000000..6b001188a2 --- /dev/null +++ b/usvm-core/src/test/kotlin/org/usvm/ModelCompositionTest.kt @@ -0,0 +1,162 @@ +package org.usvm + +import io.mockk.mockk +import kotlinx.collections.immutable.persistentMapOf +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import kotlin.test.assertSame + +class ModelCompositionTest { + private lateinit var ctx: UContext + private lateinit var concreteNull: UConcreteHeapRef + + @BeforeEach + fun initializeContext() { + ctx = UContext() + concreteNull = ctx.mkConcreteHeapRef(UAddressCounter.NULL_ADDRESS) + } + + @Test + fun testComposeAllocatedArray() = with(ctx) { + val heapEvaluator = UHeapModel( + concreteNull, + mapOf(), + mapOf(), + mapOf(), + ) + + val stackModel = URegistersStackModel(concreteNull, mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) + + val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + + val region = emptyAllocatedArrayRegion(mockk(), 1, bv32Sort) + .write(0.toBv(), 0.toBv(), trueExpr) + .write(1.toBv(), 1.toBv(), trueExpr) + .write(mkRegisterReading(1, sizeSort), 2.toBv(), trueExpr) + .write(mkRegisterReading(2, sizeSort), 3.toBv(), trueExpr) + val reading = region.read(mkRegisterReading(0, sizeSort)) + + val expr = composer.compose(reading) + assertSame(mkBv(2), expr) + } + + @Test + fun testComposeRangedUpdate() = with(ctx) { + val arrayType = mockk() + val composedSymbolicHeapRef = ctx.mkConcreteHeapRef(-1) + val inputArray = UMemory2DArray(persistentMapOf((composedSymbolicHeapRef to mkBv(0)) to mkBv(1)), mkBv(0)) + val heapEvaluator = UHeapModel( + concreteNull, + mapOf(), + mapOf(arrayType to inputArray), + mapOf(), + ) + + val stackModel = + URegistersStackModel(concreteNull, mapOf(0 to composedSymbolicHeapRef, 1 to mkBv(0))) + val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + + val symbolicRef = mkRegisterReading(0, addressSort) + + val fromRegion = emptyInputArrayRegion(arrayType, bv32Sort) + + val concreteRef = mkConcreteHeapRef(1) + + val keyConverter = UInputToAllocatedKeyConverter(symbolicRef to mkBv(0), concreteRef to mkBv(0), mkBv(5)) + val concreteRegion = emptyAllocatedArrayRegion(arrayType, concreteRef.address, bv32Sort) + .copyRange(fromRegion, mkBv(0), mkBv(5), keyConverter, trueExpr) + + val idx = mkRegisterReading(1, sizeSort) + + val reading = concreteRegion.read(idx) + + val expr = composer.compose(reading) + assertSame(mkBv(1), expr) + } + + @Test + fun testComposeInputArrayLength() = with(ctx) { + val symbolicRef0 = mkRegisterReading(0, addressSort) + val symbolicRef1 = mkRegisterReading(1, addressSort) + val symbolicRef2 = mkRegisterReading(2, addressSort) + val symbolicRef3 = mkRegisterReading(3, addressSort) + + val composedRef0 = mkConcreteHeapRef(-1) + val composedRef1 = mkConcreteHeapRef(-2) + val composedRef2 = mkConcreteHeapRef(-3) + val composedRef3 = mkConcreteHeapRef(-4) + + val arrayType = mockk() + val inputLength = UMemory1DArray(persistentMapOf(composedRef0 to mkBv(42)), mkBv(0)) + val heapEvaluator = UHeapModel( + concreteNull, + mapOf(), + mapOf(), + mapOf(arrayType to inputLength), + ) + + val stackModel = URegistersStackModel( + concreteNull, + mapOf( + 0 to composedRef0, + 1 to composedRef1, + 2 to composedRef2, + 3 to composedRef3 + ) + ) + + val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + + val region = emptyInputArrayLengthRegion(arrayType, bv32Sort) + .write(symbolicRef1, 0.toBv(), trueExpr) + .write(symbolicRef2, 1.toBv(), trueExpr) + .write(symbolicRef3, 2.toBv(), trueExpr) + val reading = region.read(symbolicRef0) + + val expr = composer.compose(reading) + assertSame(mkBv(42), expr) + } + + @Test + fun testComposeInputField() = with(ctx) { + val symbolicRef0 = mkRegisterReading(0, addressSort) + val symbolicRef1 = mkRegisterReading(1, addressSort) + val symbolicRef2 = mkRegisterReading(2, addressSort) + val symbolicRef3 = mkRegisterReading(3, addressSort) + + val composedRef0 = mkConcreteHeapRef(-1) + val composedRef1 = mkConcreteHeapRef(-2) + val composedRef2 = mkConcreteHeapRef(-3) + val composedRef3 = mkConcreteHeapRef(-4) + + val field = mockk() + val inputField = UMemory1DArray(persistentMapOf(composedRef0 to composedRef0), concreteNull) + val heapEvaluator = UHeapModel( + concreteNull, + mapOf(field to inputField), + mapOf(), + mapOf(), + ) + + val stackModel = URegistersStackModel( + concreteNull, + mapOf( + 0 to composedRef0, + 1 to composedRef1, + 2 to composedRef2, + 3 to composedRef3 + ) + ) + + val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + + val region = emptyInputFieldRegion(field, addressSort) + .write(symbolicRef1, symbolicRef1, trueExpr) + .write(symbolicRef2, symbolicRef2, trueExpr) + .write(symbolicRef3, symbolicRef3, trueExpr) + val reading = region.read(symbolicRef0) + + val expr = composer.compose(reading) + assertSame(composedRef0, expr) + } +} \ No newline at end of file diff --git a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt index bbd560a8d4..629464a92e 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt @@ -22,4 +22,5 @@ class ModelDecodingTest { val status = solver.check(UMemoryBase(this, mockk()), UPathConstraintsSet(persistentSetOf(trueExpr))) assertIs>>(status) } + } diff --git a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt index a724e91045..cf3b7a6a99 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt @@ -193,7 +193,7 @@ class TranslationTest { val ref2 = mkRegisterReading(2, addressSort) val ref3 = mkRegisterReading(3, addressSort) - val region = emptyArrayLengthRegion(mockk(), bv32Sort) + val region = emptyInputArrayLengthRegion(mockk(), bv32Sort) .write(ref1, mkBv(1), trueExpr) .write(ref2, mkBv(2), trueExpr) .write(ref3, mkBv(3), trueExpr) From 522f953eec1d99f5b32fb381ca0fbc5a6a011da5 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 18 Apr 2023 11:05:44 +0300 Subject: [PATCH 23/28] Fix: tests --- usvm-core/src/main/kotlin/org/usvm/Context.kt | 3 +- .../main/kotlin/org/usvm/ExprTranslator.kt | 2 +- .../src/main/kotlin/org/usvm/Expressions.kt | 2 +- usvm-core/src/main/kotlin/org/usvm/Heap.kt | 10 +- .../main/kotlin/org/usvm/HeapRefSplitting.kt | 12 +-- usvm-core/src/main/kotlin/org/usvm/Memory.kt | 12 +-- .../src/main/kotlin/org/usvm/ModelDecoder.kt | 6 +- usvm-core/src/main/kotlin/org/usvm/Solver.kt | 2 +- .../test/kotlin/org/usvm/CompositionTest.kt | 4 +- .../src/test/kotlin/org/usvm/HeapRefEqTest.kt | 22 ++--- .../kotlin/org/usvm/HeapRefSplittingTest.kt | 38 +++---- .../test/kotlin/org/usvm/ModelDecodingTest.kt | 99 ++++++++++++++++++- .../test/kotlin/org/usvm/TranslationTest.kt | 8 +- 13 files changed, 154 insertions(+), 66 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/Context.kt b/usvm-core/src/main/kotlin/org/usvm/Context.kt index ce79300913..b51d3237eb 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Context.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Context.kt @@ -4,6 +4,7 @@ import org.ksmt.KAst import org.ksmt.KContext import org.ksmt.expr.KConst import org.ksmt.expr.KExpr +import org.ksmt.expr.KUninterpretedSortValue import org.ksmt.sort.KBoolSort import org.ksmt.sort.KSort import org.ksmt.sort.KSortVisitor @@ -51,7 +52,7 @@ open class UContext( * @return the new equal rewritten expression without [UConcreteHeapRef]s */ override fun mkEq(lhs: KExpr, rhs: KExpr, order: Boolean): KExpr = - if (lhs.sort == addressSort && lhs !is KConst<*> && rhs !is KConst<*>) { + if (lhs.sort == addressSort) { mkHeapRefEq(lhs.asExpr(addressSort), rhs.asExpr(addressSort)) } else { super.mkEq(lhs, rhs, order) diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index d0e1c37234..e297748e96 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -34,7 +34,7 @@ open class UExprTranslator constructor( } override fun transform(expr: UNullRef): UExpr { - val const = expr.sort.mkConst("null") + val const = ctx.mkUninterpretedSortValue(ctx.addressSort, 0) return const } diff --git a/usvm-core/src/main/kotlin/org/usvm/Expressions.kt b/usvm-core/src/main/kotlin/org/usvm/Expressions.kt index 2f130a7aa1..e5eb5927a6 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Expressions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Expressions.kt @@ -264,7 +264,7 @@ class UIndexedMethodReturnValue internal constructor( } override fun print(printer: ExpressionPrinter) { - TODO("Not yet implemented") + printer.append("$method:#$callIndex") } override fun internEquals(other: Any): Boolean = structurallyEqual(other, { method }, { callIndex }, { sort }) diff --git a/usvm-core/src/main/kotlin/org/usvm/Heap.kt b/usvm-core/src/main/kotlin/org/usvm/Heap.kt index 86ad622b31..d63e46daa3 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Heap.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Heap.kt @@ -46,8 +46,8 @@ interface UHeap : guard: Guard, ) - fun allocate(): UConcreteHeapAddress - fun allocateArray(count: SizeT): UConcreteHeapAddress + fun allocate(): UConcreteHeapRef + fun allocateArray(count: SizeT): UConcreteHeapRef } typealias USymbolicHeap = UHeap, USizeExpr, Field, ArrayType, UBoolExpr> @@ -302,12 +302,12 @@ data class URegionHeap( ) } - override fun allocate() = lastAddress.freshAddress() + override fun allocate() = ctx.mkConcreteHeapRef(lastAddress.freshAddress()) - override fun allocateArray(count: USizeExpr): UConcreteHeapAddress { + override fun allocateArray(count: USizeExpr): UConcreteHeapRef { val address = lastAddress.freshAddress() allocatedLengths = allocatedLengths.put(address, count) - return address + return ctx.mkConcreteHeapRef(address) } override fun nullRef(): UHeapRef = ctx.nullRef diff --git a/usvm-core/src/main/kotlin/org/usvm/HeapRefSplitting.kt b/usvm-core/src/main/kotlin/org/usvm/HeapRefSplitting.kt index 0494fd6d12..f6d0f637b0 100644 --- a/usvm-core/src/main/kotlin/org/usvm/HeapRefSplitting.kt +++ b/usvm-core/src/main/kotlin/org/usvm/HeapRefSplitting.kt @@ -30,12 +30,12 @@ internal fun splitUHeapRef(ref: UHeapRef, initialGuard: UBoolExpr = ref.ctx.true val concreteHeapRefs = mutableListOf>() val symbolicHeapRef = filter(ref, initialGuard) { guarded -> - if (guarded.expr is USymbolicHeapRef) { - true - } else { + if (guarded.expr is UConcreteHeapRef) { @Suppress("UNCHECKED_CAST") concreteHeapRefs += (guarded as GuardedExpr) false + } else { + true } } @@ -150,9 +150,6 @@ internal inline fun filter( crossinline predicate: (GuardedExpr) -> Boolean, ): GuardedExpr? = with(ref.ctx) { when (ref) { - is USymbolicHeapRef, - is UConcreteHeapRef, - -> (ref with initialGuard).takeIf(predicate) is UIteExpr -> { /** * This code simulates DFS on a binary tree without an explicit recursion. Pair.second represents the first @@ -225,7 +222,6 @@ internal inline fun filter( completelyMapped.single()?.withAlso(initialGuard) } - - else -> error("Unexpected ref: $ref") + else -> (ref with initialGuard).takeIf(predicate) } } \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/Memory.kt b/usvm-core/src/main/kotlin/org/usvm/Memory.kt index cf91f1ba62..f262946b54 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Memory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Memory.kt @@ -91,15 +91,15 @@ open class UMemoryBase( } override fun alloc(type: Type): UHeapRef { - val address = heap.allocate() - types.allocate(address, type) - return ctx.mkConcreteHeapRef(address) + val concreteHeapRef = heap.allocate() + types.allocate(concreteHeapRef.address, type) + return concreteHeapRef } override fun malloc(arrayType: Type, count: USizeExpr): UHeapRef { - val address = heap.allocateArray(count) - types.allocate(address, arrayType) - return ctx.mkConcreteHeapRef(address) + val concreteHeapRef = heap.allocateArray(count) + types.allocate(concreteHeapRef.address, arrayType) + return concreteHeapRef } override fun memset(ref: UHeapRef, arrayType: Type, elementSort: USort, contents: Sequence>) = diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt index e79d243f17..728ff96deb 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt @@ -300,18 +300,18 @@ class UHeapModel( fromDstIdx: USizeExpr, toDstIdx: USizeExpr, guard: UBoolExpr, - ): Unit = error("Illegal operation for a model") + ) = error("Illegal operation for a model") override fun memset( ref: UHeapRef, type: ArrayType, sort: Sort, contents: Sequence>, - ): Unit = error("Illegal operation for a model") + ) = error("Illegal operation for a model") override fun allocate() = error("Illegal operation for a model") - override fun allocateArray(count: USizeExpr): UConcreteHeapAddress = error("Illegal operation for a model") + override fun allocateArray(count: USizeExpr) = error("Illegal operation for a model") override fun nullRef(): UConcreteHeapRef = nullRef diff --git a/usvm-core/src/main/kotlin/org/usvm/Solver.kt b/usvm-core/src/main/kotlin/org/usvm/Solver.kt index 95992c590f..1a4f4b0816 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Solver.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Solver.kt @@ -31,7 +31,7 @@ open class USolverBase( solver.push() for (constraint in pc) { - val translated = translator.apply(constraint) + val translated = translator.translate(constraint) solver.assert(translated) } diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index 51c7c733a2..0ccb689386 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -246,7 +246,7 @@ internal class CompositionTest { val sndIndex = mockk() val keyEqualityComparer = { k1: USymbolicArrayIndex, k2: USymbolicArrayIndex -> - mkAnd(k1.first eq k2.first, k1.second eq k2.second) + mkAnd((k1.first == k2.first).expr, (k1.second == k2.second).expr) } val updates = UFlatUpdates( @@ -408,7 +408,7 @@ internal class CompositionTest { val bAddress = mockk() val updates = UFlatUpdates( - symbolicEq = { k1, k2 -> k1 eq k2 }, + symbolicEq = { k1, k2 -> (k1 == k2).expr }, concreteCmp = { _, _ -> throw UnsupportedOperationException() }, symbolicCmp = { _, _ -> throw UnsupportedOperationException() } ) diff --git a/usvm-core/src/test/kotlin/org/usvm/HeapRefEqTest.kt b/usvm-core/src/test/kotlin/org/usvm/HeapRefEqTest.kt index cb8db1eb3f..abc929933a 100644 --- a/usvm-core/src/test/kotlin/org/usvm/HeapRefEqTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/HeapRefEqTest.kt @@ -33,7 +33,7 @@ class HeapRefEqTest { @Test fun testSymbolicAndConcreteHeapRefEq() = with(ctx) { - val ref1 = mkConcreteHeapRef(heap.allocate()) + val ref1 = heap.allocate() val ref2 = mkRegisterReading(0, addressSort) val expr = mkHeapRefEq(ref1, ref2) assertSame(falseExpr, expr) @@ -49,15 +49,15 @@ class HeapRefEqTest { @Test fun testConcreteHeapRefEqFalse() = with(ctx) { - val ref1 = mkConcreteHeapRef(heap.allocate()) - val ref2 = mkConcreteHeapRef(heap.allocate()) + val ref1 = heap.allocate() + val ref2 = heap.allocate() val expr = mkHeapRefEq(ref1, ref2) assertSame(falseExpr, expr) } @Test fun testConcreteHeapRefEqTrue() = with(ctx) { - val ref1 = mkConcreteHeapRef(heap.allocate()) + val ref1 = heap.allocate() val ref2 = ref1 val expr = mkHeapRefEq(ref1, ref2) assertSame(trueExpr, expr) @@ -65,7 +65,7 @@ class HeapRefEqTest { @Test fun testInterleavedWithNullHeapRefEq() = with(ctx) { - val ref1 = mkConcreteHeapRef(heap.allocate()) + val ref1 = heap.allocate() val ref2 = mkNullRef() val expr = mkHeapRefEq(ref1, ref2) assertSame(falseExpr, expr) @@ -73,7 +73,7 @@ class HeapRefEqTest { @Test fun testInterleavedHeapRefEq() = with(ctx) { - val ref1 = mkConcreteHeapRef(heap.allocate()) + val ref1 = heap.allocate() val ref2 = mkRegisterReading(0, addressSort) val expr = mkHeapRefEq(ref1, ref2) assertSame(falseExpr, expr) @@ -81,11 +81,11 @@ class HeapRefEqTest { @Test fun testSimpleIteConcreteHeapRefEq() = with(ctx) { - val ref1 = mkConcreteHeapRef(heap.allocate()) + val ref1 = heap.allocate() val ref2 = ref1 val cond1 by boolSort - val ref3 = mkConcreteHeapRef(heap.allocate()) + val ref3 = heap.allocate() val ref4 = ref3 val cond2 by boolSort @@ -110,7 +110,7 @@ class HeapRefEqTest { @Test fun testSimpleIteHeapRefEq() = with(ctx) { - val ref1 = mkConcreteHeapRef(heap.allocate()) + val ref1 = heap.allocate() val ref2 = mkRegisterReading(0, addressSort) val cond1 by boolSort @@ -129,8 +129,8 @@ class HeapRefEqTest { val symbolicRef2 = mkRegisterReading(1, addressSort) val symbolicRef3 = mkRegisterReading(2, addressSort) - val concreteRef1 = mkConcreteHeapRef(heap.allocate()) - val concreteRef2 = mkConcreteHeapRef(heap.allocate()) + val concreteRef1 = heap.allocate() + val concreteRef2 = heap.allocate() val cond1 by boolSort val cond2 by boolSort diff --git a/usvm-core/src/test/kotlin/org/usvm/HeapRefSplittingTest.kt b/usvm-core/src/test/kotlin/org/usvm/HeapRefSplittingTest.kt index 316d5f1bfb..e8a73eb296 100644 --- a/usvm-core/src/test/kotlin/org/usvm/HeapRefSplittingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/HeapRefSplittingTest.kt @@ -29,8 +29,8 @@ class HeapRefSplittingTest { @Test fun testConcreteWriting() = with(ctx) { - val ref1 = mkConcreteHeapRef(heap.allocate()) - val ref2 = mkConcreteHeapRef(heap.allocate()) + val ref1 = heap.allocate() + val ref2 = heap.allocate() val value1 = mkBv(0) val value2 = mkBv(1) @@ -47,7 +47,7 @@ class HeapRefSplittingTest { @Test fun testIteWriting() = with(ctx) { - val ref1 = mkConcreteHeapRef(heap.allocate()) + val ref1 = heap.allocate() val ref2 = mkRegisterReading(0, addressSort) val cond by boolSort @@ -69,9 +69,9 @@ class HeapRefSplittingTest { @Test fun testInterleavedWritingToArray(): Unit = with(ctx) { - val arrayRef = mkConcreteHeapRef(heap.allocate()) + val arrayRef = heap.allocate() - val ref1 = mkConcreteHeapRef(heap.allocate()) + val ref1 = heap.allocate() val ref2 = mkRegisterReading(0, addressSort) val idx1 by sizeSort @@ -95,15 +95,15 @@ class HeapRefSplittingTest { @Test fun testSeveralWritingsToArray() = with(ctx) { - val ref = mkConcreteHeapRef(heap.allocate()) + val ref = heap.allocate() val idx1 = mkRegisterReading(0, sizeSort) val idx2 = mkRegisterReading(1, sizeSort) val idx3 = mkRegisterReading(2, sizeSort) - val val1 = mkConcreteHeapRef(heap.allocate()) - val val2 = mkConcreteHeapRef(heap.allocate()) - val val3 = mkConcreteHeapRef(heap.allocate()) + val val1 = heap.allocate() + val val2 = heap.allocate() + val val3 = heap.allocate() heap.writeArrayIndex(ref, idx1, arrayDescr.first, arrayDescr.second, val1, trueExpr) heap.writeArrayIndex(ref, idx2, arrayDescr.first, arrayDescr.second, val2, trueExpr) @@ -121,16 +121,16 @@ class HeapRefSplittingTest { @Test fun testWritingIteToArrayByIteIndex() = with(ctx) { - val ref1 = mkConcreteHeapRef(heap.allocate()) - val ref2 = mkConcreteHeapRef(heap.allocate()) + val ref1 = heap.allocate() + val ref2 = heap.allocate() val cond1 by boolSort val ref = mkIte(cond1, ref1, ref2) val idx = mkRegisterReading(0, sizeSort) - val val1 = mkConcreteHeapRef(heap.allocate()) - val val2 = mkConcreteHeapRef(heap.allocate()) + val val1 = heap.allocate() + val val2 = heap.allocate() val cond2 by boolSort val value = mkIte(cond2, val1, val2) @@ -155,9 +155,9 @@ class HeapRefSplittingTest { val ref2 = mkRegisterReading(1, addressSort) val ref3 = mkRegisterReading(2, addressSort) - val val1 = mkConcreteHeapRef(heap.allocate()) + val val1 = heap.allocate() val val2 = mkRegisterReading(3, addressSort) - val val3 = mkConcreteHeapRef(heap.allocate()) + val val3 = heap.allocate() heap.writeField(ref1, addressFieldDescr.first, addressFieldDescr.second, val1, trueExpr) heap.writeField(ref2, addressFieldDescr.first, addressFieldDescr.second, val2, trueExpr) @@ -175,7 +175,7 @@ class HeapRefSplittingTest { @Test fun testInterleavedWritingToArrayButNoMatchedUpdates() = with(ctx) { - val arrayRef = mkConcreteHeapRef(heap.allocate()) + val arrayRef = heap.allocate() val ref1 = mkRegisterReading(0, addressSort) val ref2 = mkRegisterReading(1, addressSort) @@ -198,7 +198,7 @@ class HeapRefSplittingTest { val res2 = heap.readField(ref2, valueFieldDescr.first, valueFieldDescr.second) assertIs>(res2) - assertSame(res1.region, res2.region) + assertEquals(res1.region, res2.region) } @Test @@ -221,7 +221,7 @@ class HeapRefSplittingTest { val res2 = heap.readField(ref2, addressFieldDescr.first, addressFieldDescr.second) assertIs>(res2) - assertSame(res1.region, res2.region) + assertEquals(res1.region, res2.region) val res3 = heap.readField(ref3, addressFieldDescr.first, addressFieldDescr.second) assertSame(val3, res3) @@ -230,7 +230,7 @@ class HeapRefSplittingTest { @Test fun testInterleavedValueWriting() = with(ctx) { val ref1 = mkRegisterReading(0, addressSort) - val ref2 = mkConcreteHeapRef(heap.allocate()) + val ref2 = heap.allocate() val cond by boolSort diff --git a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt index 629464a92e..adee8cdcb5 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt @@ -1,26 +1,117 @@ package org.usvm import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertSame import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.ksmt.solver.z3.KZ3Solver import kotlin.test.assertIs -import kotlinx.collections.immutable.persistentSetOf class ModelDecodingTest { private lateinit var ctx: UContext + private lateinit var solver: USolverBase + + private lateinit var memory: UMemoryBase + private lateinit var stack: URegistersStack + private lateinit var heap: URegionHeap + private lateinit var mocker: UIndexedMocker @BeforeEach fun initializeContext() { ctx = UContext() + val (translator, decoder) = buildDefaultTranslatorAndDecoder(ctx) + solver = USolverBase(ctx, KZ3Solver(ctx), translator, decoder) + + stack = URegistersStack(ctx) + stack.push(10) + heap = URegionHeap(ctx) + mocker = UIndexedMocker(ctx) + + memory = UMemoryBase(ctx, mockk(), stack, heap, mockk(), mocker) } @Test fun testSmoke(): Unit = with(ctx) { - val (translator, decoder) = buildDefaultTranslatorAndDecoder(ctx) - val solver = USolverBase(this, KZ3Solver(this), translator, decoder) - val status = solver.check(UMemoryBase(this, mockk()), UPathConstraintsSet(persistentSetOf(trueExpr))) + val status = solver.check(memory, UPathConstraintsSet(trueExpr)) assertIs>>(status) } + @Test + fun testSimpleWritingToFields() = with(ctx) { + val field = mockk() + + val concreteRef = heap.allocate() + val symbolicRef0 = stack.readRegister(0, addressSort) + val symbolicRef1 = stack.readRegister(1, addressSort) + + heap.writeField(concreteRef, field, bv32Sort, mkBv(1), trueExpr) + heap.writeField(symbolicRef0, field, bv32Sort, mkBv(2), trueExpr) + + val pc = heap.readField(symbolicRef1, field, bv32Sort) eq mkBv(42) + + val status = solver.check(memory, UPathConstraintsSet(pc)) + val model = assertIs>>(status).model + + val expr = heap.readField(symbolicRef1, field, bv32Sort) + + assertSame(mkBv(42), model.eval(expr)) + } + + @Test + fun testSimpleWritingToAddressFields() = with(ctx) { + val field = mockk() + + val concreteRef = heap.allocate() + val symbolicRef0 = stack.readRegister(0, addressSort) + val symbolicRef1 = stack.readRegister(1, addressSort) + + heap.writeField(concreteRef, field, addressSort, symbolicRef1, trueExpr) + heap.writeField(symbolicRef0, field, addressSort, symbolicRef0, trueExpr) + + val pc = (symbolicRef1 neq nullRef) and (symbolicRef0 neq nullRef) and + (heap.readField(symbolicRef0, field, addressSort) eq symbolicRef1) + + val status = solver.check(memory, UPathConstraintsSet(pc)) + val model = assertIs>>(status).model + + val expr = heap.readField(symbolicRef1, field, addressSort) + + assertSame(model.eval(symbolicRef0), model.eval(expr)) + } + + @Test + fun testSimpleMock() = with(ctx) { + val field = mockk() + val method = mockk() + + val mockedValue = mocker.call(method, emptySequence(), addressSort).first + val ref1 = heap.readField(mockedValue, field, addressSort) + heap.writeField(ref1, field, addressSort, heap.allocate(), trueExpr) + val ref2 = heap.readField(mockedValue, field, addressSort) + + val pc = (ref1 neq ref2) and (mockedValue neq nullRef) and (ref1 neq nullRef) + + val status = solver.check(memory, UPathConstraintsSet(pc)) + val model = assertIs>>(status).model + + val mockedValueEqualsRef1 = mockedValue eq ref1 + + assertSame(trueExpr, model.eval(mockedValueEqualsRef1)) + } + + @Test + fun testSimpleMockUnsat(): Unit = with(ctx) { + val field = mockk() + val method = mockk() + + val mockedValue = mocker.call(method, emptySequence(), addressSort).first + val ref1 = heap.readField(mockedValue, field, addressSort) + heap.writeField(ref1, field, addressSort, ref1, trueExpr) + val ref2 = heap.readField(mockedValue, field, addressSort) + + val pc = (ref1 neq ref2) and (mockedValue neq nullRef) and (ref1 neq nullRef) + + val status = solver.check(memory, UPathConstraintsSet(pc)) + assertIs>>(status) + } } diff --git a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt index cf3b7a6a99..7cd4d4d401 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TranslationTest.kt @@ -34,7 +34,7 @@ class TranslationTest { @Test fun testTranslateConstAddressSort() = with(ctx) { - val ref = mkConcreteHeapRef(heap.allocate()) + val ref = heap.allocate() val idx = mkRegisterReading(0, sizeSort) val expr = heap.readArrayIndex(ref, idx, addressArrayDescr, addressSort) @@ -45,7 +45,7 @@ class TranslationTest { @Test fun testTranslateConstValueSort() = with(ctx) { - val ref = mkConcreteHeapRef(heap.allocate()) + val ref = heap.allocate() val idx = mkRegisterReading(0, sizeSort) val expr = heap.readArrayIndex(ref, idx, valueArrayDescr, bv32Sort) @@ -56,7 +56,7 @@ class TranslationTest { @Test fun testTranslateWritingsToAllocatedArray() = with(ctx) { - val ref = mkConcreteHeapRef(heap.allocate()) + val ref = heap.allocate() val idx1 = mkRegisterReading(0, sizeSort) val idx2 = mkRegisterReading(1, sizeSort) @@ -130,7 +130,7 @@ class TranslationTest { .write(ref1 to idx1, val1, trueExpr) .write(ref2 to idx2, val2, trueExpr) - val concreteRef = mkConcreteHeapRef(heap.allocate()) + val concreteRef = heap.allocate() val keyConverter = UInputToAllocatedKeyConverter(ref1 to mkBv(0), concreteRef to mkBv(0), mkBv(5)) From 5c2a5f7abdb0ef2956c5dc27828a271448eb442d Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 18 Apr 2023 12:50:02 +0300 Subject: [PATCH 24/28] Add: tests and docs --- .../main/kotlin/org/usvm/ExprTranslator.kt | 28 ++++--- .../main/kotlin/org/usvm/HeapRefSplitting.kt | 5 +- .../src/main/kotlin/org/usvm/MemoryRegions.kt | 10 +-- .../src/main/kotlin/org/usvm/MemoryUpdates.kt | 14 +++- usvm-core/src/main/kotlin/org/usvm/Model.kt | 8 +- .../src/main/kotlin/org/usvm/ModelDecoder.kt | 76 +++++++++++-------- .../src/main/kotlin/org/usvm/ModelRegions.kt | 12 ++- .../src/main/kotlin/org/usvm/RegionIds.kt | 53 ++++--------- .../main/kotlin/org/usvm/RegionTranslator.kt | 23 +++--- .../test/kotlin/org/usvm/CompositionTest.kt | 2 +- .../test/kotlin/org/usvm/ModelDecodingTest.kt | 29 +++++++ 11 files changed, 142 insertions(+), 118 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index e297748e96..3ab3c67e47 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -6,9 +6,9 @@ import org.ksmt.sort.KArraySortBase import org.ksmt.utils.cast import org.ksmt.utils.mkConst -open class UExprTranslator constructor( +open class UExprTranslator( override val ctx: UContext, -) : UExprTransformer(ctx), URegionIdTranslatorFactory { +) : UExprTransformer(ctx), URegionIdVisitor> { open fun translate(expr: UExpr): UExpr = apply(expr) @@ -106,7 +106,14 @@ open class UExprTranslator constructor( return URegionTranslator(updateTranslator) } - val regionIdInitialValueProvider = URegionIdInitialValueProvider(onDefaultValuePresent = { translate(it) }) + open fun buildTranslator( + regionId: URegionId, + ): URegionTranslator, Key, Sort, *> { + @Suppress("UNCHECKED_CAST") + return regionId.accept(this) as URegionTranslator, Key, Sort, *> + } + + val regionIdInitialValueProvider = URegionIdInitialValueFactoryBase(onDefaultValuePresent = { translate(it) }) } open class UCachingExprTranslator( @@ -140,20 +147,11 @@ open class UCachingExprTranslator( }.cast() } -interface URegionIdTranslatorFactory : URegionIdVisitor> { - fun buildTranslator( - regionId: URegionId, - ): URegionTranslator, Key, Sort, *> { - @Suppress("UNCHECKED_CAST") - return regionId.accept(this) as URegionTranslator, Key, Sort, *> - } -} - -typealias URegionIdInitialValueFactory = URegionIdVisitor> +typealias URegionIdInitialValueFactory = URegionIdVisitor>> -open class URegionIdInitialValueProvider( +open class URegionIdInitialValueFactoryBase( val onDefaultValuePresent: (UExpr<*>) -> UExpr<*>, -) : URegionIdVisitor>> { +) : URegionIdInitialValueFactory { override fun visit(regionId: UInputFieldId): UExpr> { require(regionId.defaultValue == null) return with(regionId.sort.uctx) { diff --git a/usvm-core/src/main/kotlin/org/usvm/HeapRefSplitting.kt b/usvm-core/src/main/kotlin/org/usvm/HeapRefSplitting.kt index f6d0f637b0..76ec02631c 100644 --- a/usvm-core/src/main/kotlin/org/usvm/HeapRefSplitting.kt +++ b/usvm-core/src/main/kotlin/org/usvm/HeapRefSplitting.kt @@ -21,7 +21,10 @@ internal data class SplitHeapRefs( /** * Traverses the [ref] non-recursively and collects [UConcreteHeapRef]s and [USymbolicHeapRef] as well as - * guards for them. The result [SplitHeapRefs.symbolicHeapRef] will be `null` if there are no [USymbolicHeapRef]s as + * guards for them. If the object is not a [UConcreteHeapRef] nor a [USymbolicHeapRef], e.g. KConst, + * treats such an object as a [USymbolicHeapRef]. + * + * The result [SplitHeapRefs.symbolicHeapRef] will be `null` if there are no [USymbolicHeapRef]s as * leafs in the [ref] ite. Otherwise, it will contain an ite with the guard protecting from bubbled up concrete refs. * * @param initialGuard an initial value for the accumulated guard. diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt index b32ef5fbca..142b9dbf24 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt @@ -7,11 +7,6 @@ import org.usvm.util.emptyRegionTree //region Memory region -/** - * A typealias for a lambda that takes a key, a region and returns a reading from the region by the key. - */ -typealias UInstantiator = (key: Key, USymbolicMemoryRegion) -> UExpr - interface UMemoryRegion { fun read(key: Key): UExpr @@ -169,9 +164,6 @@ data class USymbolicMemoryRegion, val mappedRegionId = regionId.map(composer) val mappedUpdates = updates.map(regionId.keyMapper(composer), composer) - // Note that we cannot use optimization with unchanged mappedUpdates and mappedDefaultValues here - // since in a new region we might have an updated instantiator. - // Therefore, we have to check their reference equality as well. if (mappedUpdates === updates && mappedRegionId === regionId) { return this } @@ -347,7 +339,7 @@ fun emptyAllocatedArrayRegion( updates = emptyRegionTree(), ::indexRegion, ::indexRangeRegion, ::indexEq, ::indexLeConcrete, ::indexLeSymbolic ) - val regionId = UAllocatedArrayId(arrayType, address, sort, sort.sampleValue(), contextHeap = null) + val regionId = UAllocatedArrayId(arrayType, sort, sort.sampleValue(), address, contextHeap = null) return USymbolicMemoryRegion(regionId, updates) } diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt index 5a0706cbb7..13b1ee2db8 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt @@ -75,7 +75,7 @@ interface UMemoryUpdates : Sequence> { * (from the oldest to the newest) with accumulated [Result]. * * Uses [lookupCache] to shortcut the traversal. The actual key is determined by the - * [UMemoryUpdates] implementation. It's caller's responsibility to maintain the lifetime of the [lookupCache]. + * [UMemoryUpdates] implementation. A caller is responsible to maintain the lifetime of the [lookupCache]. * * @return the final result. */ @@ -85,6 +85,18 @@ interface UMemoryUpdates : Sequence> { ): Result } +/** + * A generic memory updates visitor. Doesn't think about a cache. + */ +interface UMemoryUpdatesVisitor { + fun visitSelect(result: Result, key: Key): UExpr + + fun visitInitialValue(): Result + + fun visitUpdate(previous: Result, update: UUpdateNode): Result +} + + //region Flat memory updates diff --git a/usvm-core/src/main/kotlin/org/usvm/Model.kt b/usvm-core/src/main/kotlin/org/usvm/Model.kt index 43a2f20d0e..bd306b6426 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Model.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Model.kt @@ -8,10 +8,10 @@ interface UModel { open class UModelBase( ctx: UContext, - stack: URegistersStackModel, - heap: UReadOnlySymbolicHeap, - types: UTypeModel, - mocks: UMockEvaluator + val stack: URegistersStackModel, + val heap: UReadOnlySymbolicHeap, + val types: UTypeModel, + val mocks: UMockEvaluator ) : UModel { private val composer = UComposer(ctx, stack, heap, types, mocks) diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt index 728ff96deb..f9042c1b2d 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt @@ -1,21 +1,22 @@ package org.usvm import org.ksmt.expr.KExpr -import org.ksmt.expr.KInterpretedValue import org.ksmt.solver.KModel import org.ksmt.sort.KUninterpretedSort import org.ksmt.utils.asExpr import org.ksmt.utils.cast import org.usvm.UAddressCounter.Companion.INITIAL_INPUT_ADDRESS import org.usvm.UAddressCounter.Companion.NULL_ADDRESS -import kotlinx.collections.immutable.PersistentMap -import kotlinx.collections.immutable.toPersistentMap import org.ksmt.utils.sampleValue -interface UModelDecoder { +interface UModelDecoder { fun decode(memory: Memory, model: KModel): Model } +/** + * Initializes [UExprTranslator] and [UModelDecoder] and returns them. They bounded to the root method of the analysis, + * because of internal caches (e.g., translated register readings). + */ fun buildDefaultTranslatorAndDecoder( ctx: UContext, ): Pair, UModelDecoderBase> { @@ -34,13 +35,27 @@ fun buildDefaultTranslatorAndDecoder( typealias AddressesMapping = Map, UConcreteHeapRef> + +/** + * Base decoder suitable for decoding [KModel] to [UModelBase]. It can't be reused between different root methods, + * because of a matched translator caches. + * + * Passed parameters updates on the fly in a matched translator, so they are mutable in fact. + * + * @param registerIdxToTranslated a mapping from a register idx to a translated expression. + * @param indexedMethodReturnValueToTranslated a mapping from an indexed mock symbol to a translated expression. + * @param translatedNullRef translated null reference. + * @param translatedRegionIds a set of translated region ids. + * @param regionIdInitialValueProvider an inital value provider, the same as used in the translator, so we can build + * concrete regions from a [KModel]. + */ open class UModelDecoderBase( protected val registerIdxToTranslated: Map>, protected val indexedMethodReturnValueToTranslated: Map, UExpr<*>>, protected val translatedNullRef: UExpr, protected val translatedRegionIds: Set>, protected val regionIdInitialValueProvider: URegionIdInitialValueFactory, -) : UModelDecoder, UModelBase> { +) : UModelDecoder, UModelBase> { private val ctx: UContext = translatedNullRef.uctx /** @@ -103,6 +118,8 @@ open class UModelDecoderBase( val resolvedInputArrays = mutableMapOf>() val resolvedInputArrayLengths = mutableMapOf>() + // Performs decoding from model to concrete UMemoryRegions. + // It's an internal knowledge of initialValue for each type of URegionId, so we use .cast() here. translatedRegionIds.forEach { when (it) { is UInputFieldId<*, *> -> { @@ -203,41 +220,29 @@ class UIndexedMockModel( } } +/** + * An immutable heap model. Declared as mutable heap, for using in regions composition in [UComposer]. Any call to + * modifying operation throws an exception. + * + * Any [UHeapReading] possibly writing to this heap in its [URegionId.instantiate] call actually has empty updates, + * because localization took place, so this heap won't be mutated. + */ class UHeapModel( private val nullRef: UConcreteHeapRef, private val resolvedInputFields: Map>, private val resolvedInputArrays: Map>, private val resolvedInputLengths: Map>, ) : USymbolicHeap { - - @Suppress("UNCHECKED_CAST") - private fun inputFieldRegion(field: Field, sort: Sort): UMemoryRegion = - resolvedInputFields.getOrElse(field) { - UMemory1DArray(sort.sampleValue().nullAddress(nullRef)) - } as UMemoryRegion - - @Suppress("UNCHECKED_CAST") - private fun inputArrayRegion( - arrayType: ArrayType, - sort: Sort - ): UMemoryRegion = - resolvedInputArrays.getOrElse(arrayType) { - UMemory2DArray(sort.sampleValue().nullAddress(nullRef)) - } as UMemoryRegion - - private fun inputLengthRegion(arrayType: ArrayType, sort: USizeSort): UMemoryRegion = - resolvedInputLengths.getOrElse(arrayType) { - UMemory1DArray(sort.sampleValue()) - } - - override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr { // All the expressions in the model are interpreted, therefore, they must - // have concrete addresses. Moreover, the model known only about input values + // have concrete addresses. Moreover, the model knows only about input values // which have addresses less or equal than INITIAL_INPUT_ADDRESS require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - val region = inputFieldRegion(field, sort) + @Suppress("UNCHECKED_CAST") + val region = resolvedInputFields.getOrElse(field) { + UMemory1DArray(sort.sampleValue().nullAddress(nullRef)) + } as UMemoryRegion return region.read(ref) } @@ -249,24 +254,29 @@ class UHeapModel( sort: Sort, ): UExpr { // All the expressions in the model are interpreted, therefore, they must - // have concrete addresses. Moreover, the model known only about input values + // have concrete addresses. Moreover, the model knows only about input values // which have addresses less or equal than INITIAL_INPUT_ADDRESS require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) val key = ref to index - val region = inputArrayRegion(arrayType, sort) + @Suppress("UNCHECKED_CAST") + val region = resolvedInputArrays.getOrElse(arrayType) { + UMemory2DArray(sort.sampleValue().nullAddress(nullRef)) + } as UMemoryRegion return region.read(key) } override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType): USizeExpr { // All the expressions in the model are interpreted, therefore, they must - // have concrete addresses. Moreover, the model known only about input values + // have concrete addresses. Moreover, the model knows only about input values // which have addresses less or equal than INITIAL_INPUT_ADDRESS require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - val region = inputLengthRegion(arrayType, ref.uctx.sizeSort) + val region = resolvedInputLengths.getOrElse>(arrayType) { + UMemory1DArray(ref.uctx.sizeSort.sampleValue()) + } return region.read(ref) } diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt b/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt index 9c8692a133..3b1aad667e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt @@ -3,17 +3,15 @@ package org.usvm import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toPersistentMap -import org.ksmt.expr.KArray2Store -import org.ksmt.expr.KArrayConst -import org.ksmt.expr.KArrayStore -import org.ksmt.expr.KExpr +import org.ksmt.expr.* import org.ksmt.solver.KModel import org.ksmt.sort.KArray2Sort import org.ksmt.sort.KArraySort +import org.ksmt.utils.sampleValue /** - * A specific evaluator for one-dimensional regions generalized by a single expression of a [KeySort]. + * A specific [UMemoryRegion] for one-dimensional regions generalized by a single expression of a [KeySort]. */ class UMemory1DArray( private val values: PersistentMap, UExpr>, @@ -53,7 +51,7 @@ class UMemory1DArray( * to their concrete representation. */ operator fun invoke( - initialValue: KExpr>, + initialValue: KConst>, model: KModel, mapping: Map, ): UMemory1DArray { @@ -87,7 +85,7 @@ class UMemory1DArray( } /** - * A specific evaluator for two-dimensional regions generalized be a pair + * A specific [UMemoryRegion] for two-dimensional regions generalized by a pair * of two expressions with [Key1Sort] and [Key2Sort] sorts. */ class UMemory2DArray( diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt index b735690f71..d36567018f 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionIds.kt @@ -3,17 +3,17 @@ package org.usvm import org.ksmt.utils.asExpr /** - * An interface that represents any possible type of regions that can be used in the memory. + * Represents any possible type of regions that can be used in the memory. */ interface URegionId> { val sort: Sort + val defaultValue: UExpr? - fun instantiate(region: USymbolicMemoryRegion<@UnsafeVariance RegionId, Key, Sort>, key: Key): UExpr - fun read( - heap: UReadOnlySymbolicHeap, - key: Key, - ): UExpr + /** + * Performs a reading from a [region] by a [key]. Inheritors uses context heap in memory regions composition. + */ + fun instantiate(region: USymbolicMemoryRegion<@UnsafeVariance RegionId, Key, Sort>, key: Key): UExpr fun write( heap: USymbolicHeap, @@ -50,23 +50,19 @@ data class UInputFieldId internal constructor( override val sort: Sort, val contextHeap: USymbolicHeap?, ) : URegionId> { + override val defaultValue: UExpr? get() = null + override fun instantiate( region: USymbolicMemoryRegion, UHeapRef, Sort>, key: UHeapRef ): UExpr = if (contextHeap == null) { - sort.uctx.mkInputFieldReading(region.copy(regionId = UInputFieldId(field, sort, null)), key) + sort.uctx.mkInputFieldReading(region, key) } else { region.applyTo(contextHeap) - read(contextHeap, key) + contextHeap.readField(key, field, sort).asExpr(sort) } - @Suppress("UNCHECKED_CAST") - override fun read( - heap: UReadOnlySymbolicHeap, - key: UHeapRef, - ) = heap.readField(key, field as Field, sort).asExpr(sort) - @Suppress("UNCHECKED_CAST") override fun write( heap: USymbolicHeap, @@ -104,11 +100,12 @@ interface UArrayId internal constructor( override val arrayType: ArrayType, - val address: UConcreteHeapAddress, override val sort: Sort, override val defaultValue: UExpr, + val address: UConcreteHeapAddress, val contextHeap: USymbolicHeap<*, ArrayType>?, ) : UArrayId> { + override fun instantiate( region: USymbolicMemoryRegion, USizeExpr, Sort>, key: USizeExpr @@ -116,16 +113,8 @@ data class UAllocatedArrayId internal constructor( sort.uctx.mkAllocatedArrayReading(region, key) } else { region.applyTo(contextHeap) - read(contextHeap, key) - } - - @Suppress("UNCHECKED_CAST") - override fun read( - heap: UReadOnlySymbolicHeap, - key: USizeExpr, - ): UExpr { val ref = key.uctx.mkConcreteHeapRef(address) - return heap.readArrayIndex(ref, key, arrayType as ArrayType, sort).asExpr(sort) + contextHeap.readArrayIndex(ref, key, arrayType, sort).asExpr(sort) } @Suppress("UNCHECKED_CAST") @@ -195,15 +184,9 @@ data class UInputArrayId internal constructor( sort.uctx.mkInputArrayReading(region, key.first, key.second) } else { region.applyTo(contextHeap) - read(contextHeap, key) + contextHeap.readArrayIndex(key.first, key.second, arrayType, sort).asExpr(sort) } - @Suppress("UNCHECKED_CAST") - override fun read( - heap: UReadOnlySymbolicHeap, - key: USymbolicArrayIndex, - ): UExpr = heap.readArrayIndex(key.first, key.second, arrayType as ArrayType, sort).asExpr(sort) - @Suppress("UNCHECKED_CAST") override fun write( heap: USymbolicHeap, @@ -249,15 +232,9 @@ data class UInputArrayLengthId internal constructor( sort.uctx.mkInputArrayLengthReading(region, key) } else { region.applyTo(contextHeap) - read(contextHeap, key) + contextHeap.readArrayLength(key, arrayType) } - @Suppress("UNCHECKED_CAST") - override fun read( - heap: UReadOnlySymbolicHeap, - key: UHeapRef, - ): UExpr = heap.readArrayLength(key, arrayType as ArrayType).asExpr(sort) - @Suppress("UNCHECKED_CAST") override fun write( heap: USymbolicHeap, diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt index 10666cacd8..cea39fb283 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt @@ -6,7 +6,8 @@ import org.ksmt.utils.cast import java.util.IdentityHashMap /** - * [URegionTranslator] defines a template method that translates a region reading to a specific [UExpr] with a sort [Sort]. + * [URegionTranslator] defines a template method that translates a region reading to a specific [UExpr] with a sort + * [Sort]. */ class URegionTranslator, Key, Sort : USort, Result>( private val updateTranslator: UMemoryUpdatesVisitor, @@ -22,14 +23,12 @@ class URegionTranslator, Key, Sort : U region.updates.accept(updateTranslator, visitorCache) } -interface UMemoryUpdatesVisitor { - fun visitSelect(result: Result, key: Key): UExpr - - fun visitInitialValue(): Result - - fun visitUpdate(previous: Result, update: UUpdateNode): Result -} - +/** + * A region translator for 1-dimensional symbolic regions. + * + * @param exprTranslator defines how to perform translation on inner values. + * @param initialValue defines an initial value for a translated array. + */ internal class U1DArrayUpdateTranslate( private val exprTranslator: UExprTranslator<*, *>, private val initialValue: UExpr>, @@ -78,6 +77,12 @@ internal class U1DArrayUpdateTranslate( private val UExpr.translated get() = exprTranslator.translate(this) } +/** + * A region translator for 2-dimensional symbolic regions. + * + * @param exprTranslator defines how to perform translation on inner values. + * @param initialValue defines an initial value for a translated array. + */ internal class U2DArrayUpdateVisitor< Key1Sort : USort, Key2Sort : USort, diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index 0ccb689386..fb92758379 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -362,7 +362,7 @@ internal class CompositionTest { ).write(fstIndex, 1.toBv(), guard = trueExpr) .write(sndIndex, 2.toBv(), guard = trueExpr) - val regionId = UAllocatedArrayId(arrayType, address, bv32Sort, bv32Sort.sampleValue(), contextHeap = null) + val regionId = UAllocatedArrayId(arrayType, bv32Sort, bv32Sort.sampleValue(), address, contextHeap = null) val regionArray = UAllocatedArrayRegion( regionId, updates, diff --git a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt index adee8cdcb5..fdeed716b5 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt @@ -114,4 +114,33 @@ class ModelDecodingTest { val status = solver.check(memory, UPathConstraintsSet(pc)) assertIs>>(status) } + + @Test + fun testSimpleSeveralWritingsToArray() = with(ctx) { + val array = mockk() + + val concreteRef = heap.allocate() + val symbolicRef0 = stack.readRegister(0, addressSort) + val symbolicRef1 = stack.readRegister(1, addressSort) + val symbolicRef2 = stack.readRegister(2, addressSort) + + val concreteIdx = mkBv(3) + val idx = stack.readRegister(3, bv32Sort) + + heap.writeArrayIndex(concreteRef, concreteIdx, array, addressSort, symbolicRef1, trueExpr) + val readedRef = heap.readArrayIndex(concreteRef, idx, array, addressSort) + + val readedRef1 = heap.readArrayIndex(symbolicRef2, idx, array, addressSort) + + heap.writeArrayIndex(readedRef, idx, array, addressSort, symbolicRef0, trueExpr) + + val readedRef2 = heap.readArrayIndex(symbolicRef2, idx, array, addressSort) + + val pc = (symbolicRef2 neq nullRef) and (readedRef1 neq readedRef2) + + val status = solver.check(memory, UPathConstraintsSet(pc)) + val model = assertIs>>(status).model + + assertSame(model.eval(symbolicRef1), model.eval(symbolicRef2)) + } } From 96f7c2b9d09774ee2eda477f61557fe7f85a4408 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 18 Apr 2023 12:55:58 +0300 Subject: [PATCH 25/28] Fix: CE --- usvm-core/src/main/kotlin/org/usvm/Solver.kt | 2 +- usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt | 4 ---- usvm-core/src/test/kotlin/org/usvm/TestUtil.kt | 4 ++++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/Solver.kt b/usvm-core/src/main/kotlin/org/usvm/Solver.kt index 1a4f4b0816..883ded2d30 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Solver.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Solver.kt @@ -21,7 +21,7 @@ open class USolverBase( protected val ctx: UContext, protected val solver: KSolver<*>, protected val translator: UExprTranslator, - protected val decoder: UModelDecoder, UModelBase>, + protected val decoder: UModelDecoder, UModelBase>, ) : USolver, UPathCondition, UModelBase>() { override fun check(memory: UMemoryBase, pc: UPathCondition): USolverResult> { diff --git a/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt index 76c24da7d5..22caf7bf24 100644 --- a/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/MapCompositionTest.kt @@ -377,7 +377,3 @@ class MapCompositionTest { assertSame(expected = composedSndValue, actual = sndElement.value) } } - -fun shouldNotBeCalled(): T { - error("Should not be called") -} diff --git a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt index b0bfac17fb..c703604668 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt @@ -3,3 +3,7 @@ package org.usvm typealias Field = java.lang.reflect.Field typealias Type = kotlin.reflect.KClass<*> typealias Method = kotlin.reflect.KFunction<*> + +fun shouldNotBeCalled(): T { + error("Should not be called") +} \ No newline at end of file From 206cd0d9ad93c9e55f7d339d7638b5b87f35e198 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Tue, 18 Apr 2023 14:04:54 +0300 Subject: [PATCH 26/28] Fix: docs --- .../main/kotlin/org/usvm/ExprTranslator.kt | 52 ++++++++++++------- .../src/main/kotlin/org/usvm/MemoryUpdates.kt | 4 +- usvm-core/src/main/kotlin/org/usvm/Model.kt | 6 +++ .../src/main/kotlin/org/usvm/ModelDecoder.kt | 2 +- .../main/kotlin/org/usvm/RegionTranslator.kt | 38 ++++++++------ 5 files changed, 64 insertions(+), 38 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index 3ab3c67e47..f767ea8e77 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -1,65 +1,73 @@ package org.usvm +import org.ksmt.expr.KExpr import org.ksmt.sort.KArray2Sort import org.ksmt.sort.KArraySort import org.ksmt.sort.KArraySortBase +import org.ksmt.sort.KBoolSort import org.ksmt.utils.cast import org.ksmt.utils.mkConst +/** + * Translates custom [UExpr] to a [KExpr]. Region readings are translated via [URegionTranslator]s. + * Base version cache everything, but doesn't track translated expressions. + * + * To show semantics of the translator, we use [KExpr] as return values, though [UExpr] is a typealias for it. + */ open class UExprTranslator( override val ctx: UContext, ) : UExprTransformer(ctx), URegionIdVisitor> { - open fun translate(expr: UExpr): UExpr = apply(expr) + open fun translate(expr: UExpr): KExpr = apply(expr) - override fun transform(expr: USymbol): UExpr = + override fun transform(expr: USymbol): KExpr = error("You must override `transform` function in UExprTranslator for ${expr::class}") - override fun transform(expr: URegisterReading): UExpr { + override fun transform(expr: URegisterReading): KExpr { // TODO: we must ensure all ids are different val registerConst = expr.sort.mkConst("r${expr.idx}") return registerConst } - override fun transform(expr: UHeapReading<*, *, *>): UExpr = + override fun transform(expr: UHeapReading<*, *, *>): KExpr = error("You must override `transform` function in UExprTranslator for ${expr::class}") - override fun transform(expr: UMockSymbol): UExpr = + override fun transform(expr: UMockSymbol): KExpr = error("You must override `transform` function in UExprTranslator for ${expr::class}") - override fun transform(expr: UIndexedMethodReturnValue): UExpr { + override fun transform(expr: UIndexedMethodReturnValue): KExpr { // TODO: we must ensure all ids are different val const = expr.sort.mkConst("m${expr.method}_${expr.callIndex}") return const } - override fun transform(expr: UNullRef): UExpr { + override fun transform(expr: UNullRef): KExpr { val const = ctx.mkUninterpretedSortValue(ctx.addressSort, 0) return const } - override fun transform(expr: UConcreteHeapRef): UExpr = + override fun transform(expr: UConcreteHeapRef): KExpr = error("Unexpected UConcreteHeapRef $expr in UExprTranslator, that has to be impossible by construction!") - override fun transform(expr: UIsExpr): UBoolExpr = + override fun transform(expr: UIsExpr): KExpr = error("Unexpected UIsExpr $expr in UExprTranslator, that has to be impossible by construction!") - override fun transform(expr: UInputArrayLengthReading): USizeExpr = + override fun transform(expr: UInputArrayLengthReading): KExpr = transformExprAfterTransformed(expr, expr.address) { address -> translateRegionReading(expr.region, address) } - override fun transform(expr: UInputArrayReading): UExpr = + override fun transform(expr: UInputArrayReading): KExpr = transformExprAfterTransformed(expr, expr.address, expr.index) { address, index -> translateRegionReading(expr.region, address to index) } - override fun transform(expr: UAllocatedArrayReading): UExpr = + override fun transform(expr: UAllocatedArrayReading): KExpr = transformExprAfterTransformed(expr, expr.index) { index -> translateRegionReading(expr.region, index) } - override fun transform(expr: UInputFieldReading): UExpr = + override fun transform(expr: UInputFieldReading): KExpr = transformExprAfterTransformed(expr, expr.address) { address -> translateRegionReading(expr.region, address) } @@ -67,7 +75,7 @@ open class UExprTranslator( open fun translateRegionReading( region: USymbolicMemoryRegion, Key, Sort>, key: Key, - ): UExpr { + ): KExpr { val regionTranslator = buildTranslator(region.regionId) return regionTranslator.translateReading(region, key) } @@ -116,6 +124,9 @@ open class UExprTranslator( val regionIdInitialValueProvider = URegionIdInitialValueFactoryBase(onDefaultValuePresent = { translate(it) }) } +/** + * Tracks translated symbols. This information used in [UModelDecoderBase]. + */ open class UCachingExprTranslator( ctx: UContext, ) : UExprTranslator(ctx) { @@ -149,17 +160,20 @@ open class UCachingExprTranslator( typealias URegionIdInitialValueFactory = URegionIdVisitor>> +/** + * @param onDefaultValuePresent translates default values. + */ open class URegionIdInitialValueFactoryBase( - val onDefaultValuePresent: (UExpr<*>) -> UExpr<*>, + val onDefaultValuePresent: (UExpr<*>) -> KExpr<*>, ) : URegionIdInitialValueFactory { - override fun visit(regionId: UInputFieldId): UExpr> { + override fun visit(regionId: UInputFieldId): KExpr> { require(regionId.defaultValue == null) return with(regionId.sort.uctx) { mkArraySort(addressSort, regionId.sort).mkConst(regionId.toString()) // TODO: replace toString } } - override fun visit(regionId: UAllocatedArrayId): UExpr> { + override fun visit(regionId: UAllocatedArrayId): KExpr> { @Suppress("SENSELESS_COMPARISON") require(regionId.defaultValue != null) return with(regionId.sort.uctx) { @@ -171,14 +185,14 @@ open class URegionIdInitialValueFactoryBase( } } - override fun visit(regionId: UInputArrayId): UExpr> { + override fun visit(regionId: UInputArrayId): KExpr> { require(regionId.defaultValue == null) return with(regionId.sort.uctx) { mkArraySort(addressSort, sizeSort, regionId.sort).mkConst(regionId.toString()) // TODO: replace toString } } - override fun visit(regionId: UInputArrayLengthId): UExpr> { + override fun visit(regionId: UInputArrayLengthId): KExpr> { require(regionId.defaultValue == null) return with(regionId.sort.uctx) { mkArraySort(addressSort, sizeSort).mkConst(regionId.toString()) // TODO: replace toString diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt index 13b1ee2db8..5589db18e5 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt @@ -219,11 +219,11 @@ class UFlatUpdates private constructor( override fun lastUpdatedElementOrNull(): UUpdateNode? = node?.update override fun isEmpty(): Boolean = node == null + override fun accept( visitor: UMemoryUpdatesVisitor, lookupCache: MutableMap, - ): Result = - UFlatMemoryUpdatesFolder(visitor, lookupCache).fold() + ): Result = UFlatMemoryUpdatesFolder(visitor, lookupCache).fold() private inner class UFlatMemoryUpdatesFolder( private val visitor: UMemoryUpdatesVisitor, diff --git a/usvm-core/src/main/kotlin/org/usvm/Model.kt b/usvm-core/src/main/kotlin/org/usvm/Model.kt index bd306b6426..f05fc06271 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Model.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Model.kt @@ -6,6 +6,12 @@ interface UModel { // TODO: Eval visitor +/** + * Consists of decoded components and allows to evaluate any expression. Evaluation is done via generic composition. + * Evaluated expressions are cached within [UModelBase] instance. + * If a symbol from an expression not found inside the model, components return the default value + * of the correct sort. + */ open class UModelBase( ctx: UContext, val stack: URegistersStackModel, diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt index f9042c1b2d..6562a025f3 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt @@ -221,7 +221,7 @@ class UIndexedMockModel( } /** - * An immutable heap model. Declared as mutable heap, for using in regions composition in [UComposer]. Any call to + * An immutable heap model. Declared as mutable heap for using in regions composition in [UComposer]. Any call to * modifying operation throws an exception. * * Any [UHeapReading] possibly writing to this heap in its [URegionId.instantiate] call actually has empty updates, diff --git a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt index cea39fb283..932022c415 100644 --- a/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/RegionTranslator.kt @@ -1,18 +1,19 @@ package org.usvm +import org.ksmt.expr.KExpr import org.ksmt.sort.KArray2Sort import org.ksmt.sort.KArraySort import org.ksmt.utils.cast import java.util.IdentityHashMap /** - * [URegionTranslator] defines a template method that translates a region reading to a specific [UExpr] with a sort + * [URegionTranslator] defines a template method that translates a region reading to a specific [KExpr] with a sort * [Sort]. */ class URegionTranslator, Key, Sort : USort, Result>( private val updateTranslator: UMemoryUpdatesVisitor, ) { - fun translateReading(region: USymbolicMemoryRegion, key: Key): UExpr { + fun translateReading(region: USymbolicMemoryRegion, key: Key): KExpr { val translated = translate(region) return updateTranslator.visitSelect(translated, key) } @@ -31,17 +32,21 @@ class URegionTranslator, Key, Sort : U */ internal class U1DArrayUpdateTranslate( private val exprTranslator: UExprTranslator<*, *>, - private val initialValue: UExpr>, -) : UMemoryUpdatesVisitor, Sort, UExpr>> { - override fun visitSelect(result: UExpr>, key: UExpr): UExpr = + private val initialValue: KExpr>, +) : UMemoryUpdatesVisitor, Sort, KExpr>> { + + /** + * [key] is already translated, so we don't have to call it explicitly. + */ + override fun visitSelect(result: KExpr>, key: KExpr): KExpr = result.ctx.mkArraySelect(result, key) - override fun visitInitialValue(): UExpr> = initialValue + override fun visitInitialValue(): KExpr> = initialValue override fun visitUpdate( - previous: UExpr>, + previous: KExpr>, update: UUpdateNode, Sort>, - ): UExpr> = with(previous.uctx) { + ): KExpr> = with(previous.uctx) { when (update) { is UPinpointUpdateNode -> { val key = update.key.translated @@ -89,23 +94,24 @@ internal class U2DArrayUpdateVisitor< Sort : USort, >( private val exprTranslator: UExprTranslator<*, *>, - private val initialValue: UExpr>, -) : UMemoryUpdatesVisitor, UExpr>, Sort, UExpr>> { + private val initialValue: KExpr>, +) : UMemoryUpdatesVisitor, UExpr>, Sort, KExpr>> { + /** * [key] is already translated, so we don't have to call it explicitly. */ override fun visitSelect( - result: UExpr>, - key: Pair, UExpr>, - ): UExpr = + result: KExpr>, + key: Pair, KExpr>, + ): KExpr = result.ctx.mkArraySelect(result, key.first, key.second) - override fun visitInitialValue(): UExpr> = initialValue + override fun visitInitialValue(): KExpr> = initialValue override fun visitUpdate( - previous: UExpr>, + previous: KExpr>, update: UUpdateNode, UExpr>, Sort>, - ): UExpr> = with(previous.uctx) { + ): KExpr> = with(previous.uctx) { when (update) { is UPinpointUpdateNode -> { val key1 = update.key.first.translated From 473e315d35033adeb05f241eb9900fbc564aaf88 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Wed, 19 Apr 2023 11:59:08 +0300 Subject: [PATCH 27/28] Add: lazy KModel decoding --- .../src/main/kotlin/org/usvm/EagerModels.kt | 150 ++++++++ .../main/kotlin/org/usvm/ExprTranslator.kt | 98 ++--- usvm-core/src/main/kotlin/org/usvm/Heap.kt | 17 - .../main/kotlin/org/usvm/LazyModelDecoder.kt | 148 ++++++++ .../src/main/kotlin/org/usvm/LazyModels.kt | 216 +++++++++++ .../src/main/kotlin/org/usvm/MemoryRegions.kt | 7 +- .../src/main/kotlin/org/usvm/MemoryUpdates.kt | 20 +- usvm-core/src/main/kotlin/org/usvm/Model.kt | 2 +- .../src/main/kotlin/org/usvm/ModelDecoder.kt | 349 ------------------ .../src/main/kotlin/org/usvm/ModelRegions.kt | 11 +- usvm-core/src/main/kotlin/org/usvm/Solver.kt | 2 +- .../test/kotlin/org/usvm/CompositionTest.kt | 8 +- .../kotlin/org/usvm/ModelCompositionTest.kt | 16 +- .../test/kotlin/org/usvm/ModelDecodingTest.kt | 2 +- 14 files changed, 592 insertions(+), 454 deletions(-) create mode 100644 usvm-core/src/main/kotlin/org/usvm/EagerModels.kt create mode 100644 usvm-core/src/main/kotlin/org/usvm/LazyModelDecoder.kt create mode 100644 usvm-core/src/main/kotlin/org/usvm/LazyModels.kt delete mode 100644 usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt diff --git a/usvm-core/src/main/kotlin/org/usvm/EagerModels.kt b/usvm-core/src/main/kotlin/org/usvm/EagerModels.kt new file mode 100644 index 0000000000..0febb19635 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/EagerModels.kt @@ -0,0 +1,150 @@ +package org.usvm + +import org.ksmt.utils.asExpr +import org.ksmt.utils.sampleValue + +class URegistersStackEagerModel( + private val nullRef: UConcreteHeapRef, + private val registers: Map> +) : URegistersStackEvaluator { + override fun eval( + registerIndex: Int, + sort: Sort, + ): UExpr = registers.getOrDefault(registerIndex, sort.sampleValue().nullAddress(nullRef)).asExpr(sort) +} + +/** + * A model for an indexed mocker that stores mapping + * from mock symbols and invocation indices to expressions. + */ +class UIndexedMockEagerModel( + private val nullRef: UConcreteHeapRef, + private val values: Map, UExpr<*>>, +) : UMockEvaluator { + + override fun eval(symbol: UMockSymbol): UExpr { + require(symbol is UIndexedMethodReturnValue<*, Sort>) + + val sort = symbol.sort + + @Suppress("UNCHECKED_CAST") + val key = symbol.method as Method to symbol.callIndex + + return values.getOrDefault(key, sort.sampleValue().nullAddress(nullRef)).asExpr(sort) + } +} + +/** + * An immutable heap model. Declared as mutable heap for using in regions composition in [UComposer]. Any call to + * modifying operation throws an exception. + * + * Any [UHeapReading] possibly writing to this heap in its [URegionId.instantiate] call actually has empty updates, + * because localization took place, so this heap won't be mutated. + */ +class UHeapEagerModel( + private val nullRef: UConcreteHeapRef, + private val resolvedInputFields: Map>, + private val resolvedInputArrays: Map>, + private val resolvedInputLengths: Map>, +) : USymbolicHeap { + override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr { + // All the expressions in the model are interpreted, therefore, they must + // have concrete addresses. Moreover, the model knows only about input values + // which have addresses less or equal than INITIAL_INPUT_ADDRESS + require(ref is UConcreteHeapRef && ref.address <= UAddressCounter.INITIAL_INPUT_ADDRESS) + + @Suppress("UNCHECKED_CAST") + val region = resolvedInputFields.getOrElse(field) { + UMemory1DArray(sort.sampleValue().nullAddress(nullRef)) + } as UMemoryRegion + + return region.read(ref) + } + + override fun readArrayIndex( + ref: UHeapRef, + index: USizeExpr, + arrayType: ArrayType, + sort: Sort, + ): UExpr { + // All the expressions in the model are interpreted, therefore, they must + // have concrete addresses. Moreover, the model knows only about input values + // which have addresses less or equal than INITIAL_INPUT_ADDRESS + require(ref is UConcreteHeapRef && ref.address <= UAddressCounter.INITIAL_INPUT_ADDRESS) + + val key = ref to index + + @Suppress("UNCHECKED_CAST") + val region = resolvedInputArrays.getOrElse(arrayType) { + UMemory2DArray(sort.sampleValue().nullAddress(nullRef)) + } as UMemoryRegion + + return region.read(key) + } + + override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType): USizeExpr { + // All the expressions in the model are interpreted, therefore, they must + // have concrete addresses. Moreover, the model knows only about input values + // which have addresses less or equal than INITIAL_INPUT_ADDRESS + require(ref is UConcreteHeapRef && ref.address <= UAddressCounter.INITIAL_INPUT_ADDRESS) + + val region = resolvedInputLengths.getOrElse>(arrayType) { + UMemory1DArray(ref.uctx.sizeSort.sampleValue()) + } + + return region.read(ref) + } + + override fun writeField( + ref: UHeapRef, + field: Field, + sort: Sort, + value: UExpr, + guard: UBoolExpr, + ) = error("Illegal operation for a model") + + override fun writeArrayIndex( + ref: UHeapRef, + index: USizeExpr, + type: ArrayType, + sort: Sort, + value: UExpr, + guard: UBoolExpr, + ) = error("Illegal operation for a model") + + override fun writeArrayLength(ref: UHeapRef, size: USizeExpr, arrayType: ArrayType) = + error("Illegal operation for a model") + + override fun memcpy( + srcRef: UHeapRef, + dstRef: UHeapRef, + type: ArrayType, + elementSort: Sort, + fromSrcIdx: USizeExpr, + fromDstIdx: USizeExpr, + toDstIdx: USizeExpr, + guard: UBoolExpr, + ) = error("Illegal operation for a model") + + override fun memset( + ref: UHeapRef, + type: ArrayType, + sort: Sort, + contents: Sequence>, + ) = error("Illegal operation for a model") + + override fun allocate() = error("Illegal operation for a model") + + override fun allocateArray(count: USizeExpr) = error("Illegal operation for a model") + + override fun nullRef(): UConcreteHeapRef = nullRef + + override fun toMutableHeap(): UHeapEagerModel = this +} + +fun UExpr.nullAddress(nullRef: UConcreteHeapRef): UExpr = + if (this == uctx.nullRef) { + nullRef.asExpr(sort) + } else { + this + } \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt index f767ea8e77..8cef18631e 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTranslator.kt @@ -1,16 +1,14 @@ package org.usvm import org.ksmt.expr.KExpr -import org.ksmt.sort.KArray2Sort -import org.ksmt.sort.KArraySort -import org.ksmt.sort.KArraySortBase import org.ksmt.sort.KBoolSort import org.ksmt.utils.cast import org.ksmt.utils.mkConst /** * Translates custom [UExpr] to a [KExpr]. Region readings are translated via [URegionTranslator]s. - * Base version cache everything, but doesn't track translated expressions. + * Base version cache everything, but doesn't track translated expressions like register readings, mock symbols, etc. + * Tracking done in the [UCachingExprTranslator]. * * To show semantics of the translator, we use [KExpr] as return values, though [UExpr] is a typealias for it. */ @@ -24,8 +22,7 @@ open class UExprTranslator( error("You must override `transform` function in UExprTranslator for ${expr::class}") override fun transform(expr: URegisterReading): KExpr { - // TODO: we must ensure all ids are different - val registerConst = expr.sort.mkConst("r${expr.idx}") + val registerConst = expr.sort.mkConst("r${expr.idx}_${expr.sort}") return registerConst } @@ -36,13 +33,12 @@ open class UExprTranslator( error("You must override `transform` function in UExprTranslator for ${expr::class}") override fun transform(expr: UIndexedMethodReturnValue): KExpr { - // TODO: we must ensure all ids are different - val const = expr.sort.mkConst("m${expr.method}_${expr.callIndex}") + val const = expr.sort.mkConst("m${expr.method}_${expr.callIndex}_${expr.sort}") return const } override fun transform(expr: UNullRef): KExpr { - val const = ctx.mkUninterpretedSortValue(ctx.addressSort, 0) + val const = ctx.mkUninterpretedSortValue(ctx.addressSort, valueIdx = 0) return const } @@ -80,12 +76,23 @@ open class UExprTranslator( return regionTranslator.translateReading(region, key) } - // these functions implement URegionIdTranslatorFactory + private val regionIdToInitialValue_ = mutableMapOf, KExpr<*>>() + val regionIdToInitialValue: Map, KExpr<*>> get() = regionIdToInitialValue_ + + private inline fun > getOrPutInitialValue( + regionId: URegionId<*, *, *>, + defaultValue: () -> V + ): V = regionIdToInitialValue_.getOrPut(regionId, defaultValue) as V override fun visit( regionId: UInputFieldId, ): URegionTranslator, UHeapRef, Sort, *> { - val initialValue = regionIdInitialValueProvider.visit(regionId) + require(regionId.defaultValue == null) + val initialValue = getOrPutInitialValue(regionId) { + with(regionId.sort.uctx) { + mkArraySort(addressSort, regionId.sort).mkConst(regionId.toString()) // TODO: replace toString + } + } val updateTranslator = U1DArrayUpdateTranslate(this, initialValue) return URegionTranslator(updateTranslator) } @@ -93,7 +100,14 @@ open class UExprTranslator( override fun visit( regionId: UAllocatedArrayId, ): URegionTranslator, USizeExpr, Sort, *> { - val initialValue = regionIdInitialValueProvider.visit(regionId) + requireNotNull(regionId.defaultValue) + val initialValue = getOrPutInitialValue(regionId) { + with(regionId.sort.uctx) { + val sort = mkArraySort(sizeSort, regionId.sort) + val translatedDefaultValue = translate(regionId.defaultValue) + mkArrayConst(sort, translatedDefaultValue) + } + } val updateTranslator = U1DArrayUpdateTranslate(this, initialValue) return URegionTranslator(updateTranslator) } @@ -101,7 +115,12 @@ open class UExprTranslator( override fun visit( regionId: UInputArrayId, ): URegionTranslator, USymbolicArrayIndex, Sort, *> { - val initialValue = regionIdInitialValueProvider.visit(regionId) + require(regionId.defaultValue == null) + val initialValue = getOrPutInitialValue(regionId) { + with(regionId.sort.uctx) { + mkArraySort(addressSort, sizeSort, regionId.sort).mkConst(regionId.toString()) // TODO: replace toString + } + } val updateTranslator = U2DArrayUpdateVisitor(this, initialValue) return URegionTranslator(updateTranslator) } @@ -109,7 +128,12 @@ open class UExprTranslator( override fun visit( regionId: UInputArrayLengthId, ): URegionTranslator, UHeapRef, USizeSort, *> { - val initialValue = regionIdInitialValueProvider.visit(regionId) + require(regionId.defaultValue == null) + val initialValue = getOrPutInitialValue(regionId) { + with(regionId.sort.uctx) { + mkArraySort(addressSort, sizeSort).mkConst(regionId.toString()) // TODO: replace toString + } + } val updateTranslator = U1DArrayUpdateTranslate(this, initialValue) return URegionTranslator(updateTranslator) } @@ -120,12 +144,10 @@ open class UExprTranslator( @Suppress("UNCHECKED_CAST") return regionId.accept(this) as URegionTranslator, Key, Sort, *> } - - val regionIdInitialValueProvider = URegionIdInitialValueFactoryBase(onDefaultValuePresent = { translate(it) }) } /** - * Tracks translated symbols. This information used in [UModelDecoderBase]. + * Tracks translated symbols. This information used in [ULazyModelDecoder]. */ open class UCachingExprTranslator( ctx: UContext, @@ -156,46 +178,4 @@ open class UCachingExprTranslator( regionIdToTranslator.getOrPut(regionId) { super.buildTranslator(regionId).cast() }.cast() -} - -typealias URegionIdInitialValueFactory = URegionIdVisitor>> - -/** - * @param onDefaultValuePresent translates default values. - */ -open class URegionIdInitialValueFactoryBase( - val onDefaultValuePresent: (UExpr<*>) -> KExpr<*>, -) : URegionIdInitialValueFactory { - override fun visit(regionId: UInputFieldId): KExpr> { - require(regionId.defaultValue == null) - return with(regionId.sort.uctx) { - mkArraySort(addressSort, regionId.sort).mkConst(regionId.toString()) // TODO: replace toString - } - } - - override fun visit(regionId: UAllocatedArrayId): KExpr> { - @Suppress("SENSELESS_COMPARISON") - require(regionId.defaultValue != null) - return with(regionId.sort.uctx) { - val sort = mkArraySort(sizeSort, regionId.sort) - - @Suppress("UNCHECKED_CAST") - val value = onDefaultValuePresent(regionId.defaultValue) as UExpr - mkArrayConst(sort, value) - } - } - - override fun visit(regionId: UInputArrayId): KExpr> { - require(regionId.defaultValue == null) - return with(regionId.sort.uctx) { - mkArraySort(addressSort, sizeSort, regionId.sort).mkConst(regionId.toString()) // TODO: replace toString - } - } - - override fun visit(regionId: UInputArrayLengthId): KExpr> { - require(regionId.defaultValue == null) - return with(regionId.sort.uctx) { - mkArraySort(addressSort, sizeSort).mkConst(regionId.toString()) // TODO: replace toString - } - } } \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/Heap.kt b/usvm-core/src/main/kotlin/org/usvm/Heap.kt index d63e46daa3..fbb8e19010 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Heap.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Heap.kt @@ -111,23 +111,6 @@ data class URegionHeap( inputLengths[arrayType] ?: emptyInputArrayLengthRegion(arrayType, ctx.sizeSort) - @Suppress("UNCHECKED_CAST", "UNUSED") - fun , Key, Sort : USort> getRegion(region: RegionId): USymbolicMemoryRegion = - // TODO with visitor? - when (region) { - is UInputFieldId<*, *> -> inputFieldRegion(region.field as Field, region.sort) - is UAllocatedArrayId<*, *> -> allocatedArrayRegion( - region.arrayType as ArrayType, - region.address, - region.sort - ) - - is UInputArrayLengthId<*> -> inputArrayLengthRegion(region.arrayType as ArrayType) - is UInputArrayId<*, *> -> inputArrayRegion(region.arrayType as ArrayType, region.sort) - else -> TODO("Implement symbolic collections") - } as USymbolicMemoryRegion - - override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr = ref.map( { concreteRef -> diff --git a/usvm-core/src/main/kotlin/org/usvm/LazyModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/LazyModelDecoder.kt new file mode 100644 index 0000000000..be6cf69be6 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/LazyModelDecoder.kt @@ -0,0 +1,148 @@ +package org.usvm + +import org.ksmt.expr.KExpr +import org.ksmt.solver.KModel +import org.ksmt.sort.KUninterpretedSort +import org.ksmt.utils.asExpr +import org.usvm.UAddressCounter.Companion.INITIAL_INPUT_ADDRESS +import org.usvm.UAddressCounter.Companion.NULL_ADDRESS + +interface UModelDecoder { + fun decode(memory: Memory, model: KModel): Model +} + +/** + * Initializes [UExprTranslator] and [UModelDecoder] and returns them. We can safely reuse them while [UContext] is + * alive. + */ +fun buildTranslatorAndLazyDecoder( + ctx: UContext, +): Pair, ULazyModelDecoder> { + val translator = UCachingExprTranslator(ctx) + + val decoder = with(translator) { + ULazyModelDecoder( + registerIdxToTranslated, + indexedMethodReturnValueToTranslated, + translatedNullRef, + regionIdToTranslator.keys, + regionIdToInitialValue, + ) + } + + return translator to decoder +} + +typealias AddressesMapping = Map, UConcreteHeapRef> + + +/** + * Base decoder suitable for decoding [KModel] to [UModelBase]. It can't be reused between different root methods, + * because of a matched translator caches. + * + * Passed parameters updates on the fly in a matched translator, so they are mutable in fact. + * + * @param registerIdxToTranslated a mapping from a register idx to a translated expression. + * @param indexedMethodReturnValueToTranslated a mapping from an indexed mock symbol to a translated expression. + * @param translatedNullRef translated null reference. + * @param translatedRegionIds a set of translated region ids. + * @param regionIdToInitialValue an initial value provider, the same as used in the translator, so we can build + * concrete regions from a [KModel]. + */ +open class ULazyModelDecoder( + protected val registerIdxToTranslated: Map>, + protected val indexedMethodReturnValueToTranslated: Map, UExpr<*>>, + protected val translatedNullRef: UExpr, + protected val translatedRegionIds: Set>, + protected val regionIdToInitialValue: Map, KExpr<*>>, +) : UModelDecoder, UModelBase> { + private val ctx: UContext = translatedNullRef.uctx + + /** + * Build a mapping from instances of an uninterpreted [UAddressSort] + * to [UConcreteHeapRef] with integer addresses. It allows us to enumerate + * equivalence classes of addresses and work with their number in the future. + */ + private fun buildMapping(model: KModel): AddressesMapping { + // Null is a special value that we want to translate in any case. + val interpretedNullRef = model.eval(translatedNullRef, isComplete = true) + + val result = mutableMapOf, UConcreteHeapRef>() + // Except the null value, it has the NULL_ADDRESS + result[interpretedNullRef] = ctx.mkConcreteHeapRef(NULL_ADDRESS) + result[translatedNullRef] = ctx.mkConcreteHeapRef(NULL_ADDRESS) + + val universe = model.uninterpretedSortUniverse(ctx.addressSort) ?: return result + // All the numbers are enumerated from the INITIAL_INPUT_ADDRESS to the Int.MIN_VALUE + var counter = INITIAL_INPUT_ADDRESS + + for (interpretedAddress in universe) { + if (interpretedAddress == interpretedNullRef) { + continue + } + result[interpretedAddress] = ctx.mkConcreteHeapRef(counter--) + } + + return result + } + + override fun decode( + memory: UMemoryBase, + model: KModel, + ): UModelBase { + val addressesMapping = buildMapping(model) + + val stack = decodeStack(model, addressesMapping) + val heap = decodeHeap(model, addressesMapping) + val types = UTypeModel(ctx, memory.typeSystem, typeByAddr = emptyMap()) + val mocks = decodeMocker(model, addressesMapping) + + return UModelBase(ctx, stack, heap, types, mocks) + } + + private fun decodeStack(model: KModel, addressesMapping: AddressesMapping): ULazyRegistersStackModel = + ULazyRegistersStackModel( + model, + addressesMapping, + registerIdxToTranslated + ) + + /** + * Constructs a [ULazyHeapModel] for a heap by provided [model] and [addressesMapping]. + */ + private fun decodeHeap( + model: KModel, + addressesMapping: AddressesMapping, + ): ULazyHeapModel = ULazyHeapModel( + model, + addressesMapping.getValue(translatedNullRef), + addressesMapping, + regionIdToInitialValue, + mutableMapOf(), + mutableMapOf(), + mutableMapOf() + ) + + private fun decodeMocker( + model: KModel, + addressesMapping: AddressesMapping, + ): ULazyIndexedMockModel = + ULazyIndexedMockModel( + model, + addressesMapping, + indexedMethodReturnValueToTranslated + ) +} + +/** + * If [this] value is an instance of address expression, returns + * an expression with a corresponding concrete address, otherwise + * returns [this] unchanched. + */ +fun UExpr.mapAddress( + addressesMapping: AddressesMapping, +): UExpr = if (sort == uctx.addressSort) { + addressesMapping.getValue(asExpr(uctx.addressSort)).asExpr(sort) +} else { + this +} \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/LazyModels.kt b/usvm-core/src/main/kotlin/org/usvm/LazyModels.kt new file mode 100644 index 0000000000..e55f9dad32 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/LazyModels.kt @@ -0,0 +1,216 @@ +package org.usvm + +import org.ksmt.expr.KExpr +import org.ksmt.solver.KModel +import org.ksmt.utils.asExpr +import org.ksmt.utils.cast +import org.ksmt.utils.sampleValue + + +/** + * Since expressions from [this] might have the [UAddressSort] and therefore + * they could be uninterpreted constants, we have to replace them with + * corresponding concrete addresses from the [addressesMapping]. + */ +private fun Map>.evalAndReplace( + key: K, + model: KModel, + addressesMapping: AddressesMapping, + sort: T +): UExpr { + val value = get(key) + return if (value != null) { + model.eval(value, isComplete = true).mapAddress(addressesMapping).asExpr(sort) + } else { + sort.sampleValue().mapAddress(addressesMapping) + } +} + +/** + * A lazy model for registers. Firstly, searches for translated symbol, then evaluates it in [model]. + * + * @param registerIdxToTranslated a translated cache. + * @param model has to be detached. + */ +class ULazyRegistersStackModel( + private val model: KModel, + private val addressesMapping: AddressesMapping, + private val registerIdxToTranslated: Map> +) : URegistersStackEvaluator { + override fun eval( + registerIndex: Int, + sort: Sort, + ): UExpr = registerIdxToTranslated.evalAndReplace(key = registerIndex, model, addressesMapping, sort) +} + +/** + * A lazy model for an indexed mocker. Firstly, searches for translated symbol, then evaluates it in [model]. + * + * @param indexedMethodReturnValueToTranslated a translated cache. + * @param model has to be detached. + */ +class ULazyIndexedMockModel( + private val model: KModel, + private val addressesMapping: AddressesMapping, + private val indexedMethodReturnValueToTranslated: Map, UExpr<*>>, +) : UMockEvaluator { + + override fun eval(symbol: UMockSymbol): UExpr { + require(symbol is UIndexedMethodReturnValue<*, Sort>) + + val sort = symbol.sort + + @Suppress("UNCHECKED_CAST") + val key = symbol.method as Method to symbol.callIndex + + return indexedMethodReturnValueToTranslated.evalAndReplace(key = key, model, addressesMapping, sort) + } +} + +/** + * + * A lazy immutable heap model. Firstly, searches for decoded [UMemoryRegion], decodes it from [model] if not found, + * then evaluates a value from it. + * + * Declared as mutable heap for using in regions composition in [UComposer]. Any call to + * modifying operation throws an exception. + * + * Any [UHeapReading] possibly writing to this heap in its [URegionId.instantiate] call actually has empty updates, + * because localization took place, so this heap won't be mutated. + * + * @param regionIdToInitialValue [URegionId] to initial values cache. Need to perform decoding from [model]. + * @param model has to be detached. + */ +class ULazyHeapModel( + private val model: KModel, + private val nullRef: UConcreteHeapRef, + private val addressesMapping: AddressesMapping, + private val regionIdToInitialValue: Map, KExpr<*>>, + private val resolvedInputFields: MutableMap>, + private val resolvedInputArrays: MutableMap>, + private val resolvedInputLengths: MutableMap>, +) : USymbolicHeap { + override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr { + // All the expressions in the model are interpreted, therefore, they must + // have concrete addresses. Moreover, the model knows only about input values + // which have addresses less or equal than INITIAL_INPUT_ADDRESS + require(ref is UConcreteHeapRef && ref.address <= UAddressCounter.INITIAL_INPUT_ADDRESS) + + val resolvedRegion = resolvedInputFields[field] + val initialValue = regionIdToInitialValue[UInputFieldId(field, sort, null)] + + return when { + resolvedRegion != null -> resolvedRegion.read(ref).asExpr(sort) + initialValue != null -> { + val region = UMemory1DArray(initialValue.cast(), model, addressesMapping) + resolvedInputFields[field] = region + region.read(ref) + } + + else -> sort.sampleValue().makeNullRefConcrete(nullRef) + } + } + + override fun readArrayIndex( + ref: UHeapRef, + index: USizeExpr, + arrayType: ArrayType, + sort: Sort, + ): UExpr { + // All the expressions in the model are interpreted, therefore, they must + // have concrete addresses. Moreover, the model knows only about input values + // which have addresses less or equal than INITIAL_INPUT_ADDRESS + require(ref is UConcreteHeapRef && ref.address <= UAddressCounter.INITIAL_INPUT_ADDRESS) + + val key = ref to index + + val resolvedRegion = resolvedInputArrays[arrayType] + val initialValue = regionIdToInitialValue[UInputArrayId(arrayType, sort, null)] + + return when { + resolvedRegion != null -> resolvedRegion.read(key).asExpr(sort) + initialValue != null -> { + val region = UMemory2DArray(initialValue.cast(), model, addressesMapping) + resolvedInputArrays[arrayType] = region + region.read(key) + } + + else -> sort.sampleValue().makeNullRefConcrete(nullRef) + } + } + + override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType): USizeExpr { + // All the expressions in the model are interpreted, therefore, they must + // have concrete addresses. Moreover, the model knows only about input values + // which have addresses less or equal than INITIAL_INPUT_ADDRESS + require(ref is UConcreteHeapRef && ref.address <= UAddressCounter.INITIAL_INPUT_ADDRESS) + + val resolvedRegion = resolvedInputLengths[arrayType] + val sizeSort = ref.uctx.sizeSort + val initialValue = regionIdToInitialValue[UInputArrayLengthId(arrayType, sizeSort, null)] + + return when { + resolvedRegion != null -> resolvedRegion.read(ref) + initialValue != null -> { + val region = UMemory1DArray(initialValue.cast(), model, addressesMapping) + resolvedInputLengths[arrayType] = region + region.read(ref) + } + + else -> sizeSort.sampleValue() + } + } + + override fun writeField( + ref: UHeapRef, + field: Field, + sort: Sort, + value: UExpr, + guard: UBoolExpr, + ) = error("Illegal operation for a model") + + override fun writeArrayIndex( + ref: UHeapRef, + index: USizeExpr, + type: ArrayType, + sort: Sort, + value: UExpr, + guard: UBoolExpr, + ) = error("Illegal operation for a model") + + override fun writeArrayLength(ref: UHeapRef, size: USizeExpr, arrayType: ArrayType) = + error("Illegal operation for a model") + + override fun memcpy( + srcRef: UHeapRef, + dstRef: UHeapRef, + type: ArrayType, + elementSort: Sort, + fromSrcIdx: USizeExpr, + fromDstIdx: USizeExpr, + toDstIdx: USizeExpr, + guard: UBoolExpr, + ) = error("Illegal operation for a model") + + override fun memset( + ref: UHeapRef, + type: ArrayType, + sort: Sort, + contents: Sequence>, + ) = error("Illegal operation for a model") + + override fun allocate() = error("Illegal operation for a model") + + override fun allocateArray(count: USizeExpr) = error("Illegal operation for a model") + + override fun nullRef(): UConcreteHeapRef = nullRef + + override fun toMutableHeap(): ULazyHeapModel = this +} + +fun UExpr.makeNullRefConcrete(conreteNullRef: UConcreteHeapRef): UExpr = + if (this == uctx.nullRef) { + conreteNullRef.asExpr(sort) + } else { + this + } \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt index 142b9dbf24..158b6aad53 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryRegions.kt @@ -30,7 +30,7 @@ data class USymbolicMemoryRegion, // to save memory usage val sort: Sort get() = regionId.sort - // if we replace it with get(), we have to check it every time in read function + // If we replace it with get(), we have to check for nullability in read function. val defaultValue = regionId.defaultValue private fun read(key: Key, updates: UMemoryUpdates): UExpr { @@ -127,8 +127,8 @@ data class USymbolicMemoryRegion, * The [guardBuilder] is used to build guards for values added to [matchingWrites]. In the end, the [guardBuilder] * is updated and contains predicate indicating that the [key] can't be included in any of visited [UUpdateNode]s. * - * @return new [USymbolicMemoryRegion] without writes satisfying [predicate] or this [USymbolicMemoryRegion] if no matching writes - * were found. + * @return new [USymbolicMemoryRegion] without writes satisfying [predicate] or this [USymbolicMemoryRegion] if no + * matching writes were found. * @see [UMemoryUpdates.split], [splittingRead] */ internal fun split( @@ -168,7 +168,6 @@ data class USymbolicMemoryRegion, return this } - // Otherwise, construct a new region with mapped values and a new instantiator. return USymbolicMemoryRegion(mappedRegionId, mappedUpdates) } diff --git a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt index 5589db18e5..886aff7238 100644 --- a/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt +++ b/usvm-core/src/main/kotlin/org/usvm/MemoryUpdates.kt @@ -71,7 +71,8 @@ interface UMemoryUpdates : Sequence> { fun isEmpty(): Boolean /** - * Accepts the [visitor]. Calls `visitInitialValue` firstly, then calls `visitUpdateNode` in the chronological order + * Accepts the [visitor]. Implementations should call [UMemoryUpdatesVisitor.visitInitialValue] firstly, then call + * [UMemoryUpdatesVisitor.visitUpdateNode] in the chronological order * (from the oldest to the newest) with accumulated [Result]. * * Uses [lookupCache] to shortcut the traversal. The actual key is determined by the @@ -479,8 +480,7 @@ data class UTreeUpdates, Sort : USort>( override fun accept( visitor: UMemoryUpdatesVisitor, lookupCache: MutableMap, - ): Result = - UTreeMemoryUpdatesFolder(visitor, lookupCache).fold() + ): Result = UTreeMemoryUpdatesFolder(visitor, lookupCache).fold() private inner class UTreeMemoryUpdatesFolder( private val visitor: UMemoryUpdatesVisitor, @@ -493,6 +493,20 @@ data class UTreeUpdates, Sort : USort>( private val emittedUpdates = hashSetOf>() + /** + * [leftMostFold] visits only nodes marked by a star `*`. + * Nodes marked by an at `@` visited in [notLeftMostFold]. + * + * ``` + * * @ + * / \ + * / \ + * * @ @ @@ + * / | \ + * / @@ @@ + * * + *``` + */ private fun leftMostFold(updates: RegionTree, *>): Result { var result = cache[updates] diff --git a/usvm-core/src/main/kotlin/org/usvm/Model.kt b/usvm-core/src/main/kotlin/org/usvm/Model.kt index f05fc06271..d2a492783b 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Model.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Model.kt @@ -14,7 +14,7 @@ interface UModel { */ open class UModelBase( ctx: UContext, - val stack: URegistersStackModel, + val stack: ULazyRegistersStackModel, val heap: UReadOnlySymbolicHeap, val types: UTypeModel, val mocks: UMockEvaluator diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt deleted file mode 100644 index 6562a025f3..0000000000 --- a/usvm-core/src/main/kotlin/org/usvm/ModelDecoder.kt +++ /dev/null @@ -1,349 +0,0 @@ -package org.usvm - -import org.ksmt.expr.KExpr -import org.ksmt.solver.KModel -import org.ksmt.sort.KUninterpretedSort -import org.ksmt.utils.asExpr -import org.ksmt.utils.cast -import org.usvm.UAddressCounter.Companion.INITIAL_INPUT_ADDRESS -import org.usvm.UAddressCounter.Companion.NULL_ADDRESS -import org.ksmt.utils.sampleValue - -interface UModelDecoder { - fun decode(memory: Memory, model: KModel): Model -} - -/** - * Initializes [UExprTranslator] and [UModelDecoder] and returns them. They bounded to the root method of the analysis, - * because of internal caches (e.g., translated register readings). - */ -fun buildDefaultTranslatorAndDecoder( - ctx: UContext, -): Pair, UModelDecoderBase> { - val translator = UCachingExprTranslator(ctx) - - val decoder = UModelDecoderBase( - translator.registerIdxToTranslated, - translator.indexedMethodReturnValueToTranslated, - translator.translatedNullRef, - translator.regionIdToTranslator.keys, - translator.regionIdInitialValueProvider, - ) - - return translator to decoder -} - -typealias AddressesMapping = Map, UConcreteHeapRef> - - -/** - * Base decoder suitable for decoding [KModel] to [UModelBase]. It can't be reused between different root methods, - * because of a matched translator caches. - * - * Passed parameters updates on the fly in a matched translator, so they are mutable in fact. - * - * @param registerIdxToTranslated a mapping from a register idx to a translated expression. - * @param indexedMethodReturnValueToTranslated a mapping from an indexed mock symbol to a translated expression. - * @param translatedNullRef translated null reference. - * @param translatedRegionIds a set of translated region ids. - * @param regionIdInitialValueProvider an inital value provider, the same as used in the translator, so we can build - * concrete regions from a [KModel]. - */ -open class UModelDecoderBase( - protected val registerIdxToTranslated: Map>, - protected val indexedMethodReturnValueToTranslated: Map, UExpr<*>>, - protected val translatedNullRef: UExpr, - protected val translatedRegionIds: Set>, - protected val regionIdInitialValueProvider: URegionIdInitialValueFactory, -) : UModelDecoder, UModelBase> { - private val ctx: UContext = translatedNullRef.uctx - - /** - * Build a mapping from instances of an uninterpreted [UAddressSort] - * to [UConcreteHeapRef] with integer addresses. It allows us to enumerate - * equivalence classes of addresses and work with their number in the future. - */ - private fun buildMapping(model: KModel): AddressesMapping { - // Null is a special value that we want to translate in any case. - val interpretedNullRef = model.eval(translatedNullRef, isComplete = true) - - val result = mutableMapOf, UConcreteHeapRef>() - // Except the null value, it has the NULL_ADDRESS - result[interpretedNullRef] = ctx.mkConcreteHeapRef(NULL_ADDRESS) - result[translatedNullRef] = ctx.mkConcreteHeapRef(NULL_ADDRESS) - - val universe = model.uninterpretedSortUniverse(ctx.addressSort) ?: return result - // All the numbers are enumerated from the INITIAL_INPUT_ADDRESS to the Int.MIN_VALUE - var counter = INITIAL_INPUT_ADDRESS - - for (interpretedAddress in universe) { - if (interpretedAddress == interpretedNullRef) { - continue - } - result[interpretedAddress] = ctx.mkConcreteHeapRef(counter--) - } - - return result - } - - override fun decode( - memory: UMemoryBase, - model: KModel, - ): UModelBase { - val addressesMapping = buildMapping(model) - - val stack = decodeStack(model, addressesMapping) - val heap = decodeHeap(model, addressesMapping) - val types = UTypeModel(ctx, memory.typeSystem, typeByAddr = emptyMap()) - val mocks = decodeMocker(model, addressesMapping) - - return UModelBase(ctx, stack, heap, types, mocks) - } - - private fun decodeStack(model: KModel, addressesMapping: AddressesMapping): URegistersStackModel { - val registers = registerIdxToTranslated.replaceUninterpretedConsts(model, addressesMapping) - - return URegistersStackModel(addressesMapping.getValue(translatedNullRef), registers) - } - - /** - * Constructs a [UHeapModel] for a heap by provided [model] and [addressesMapping]. - */ - @Suppress("UNCHECKED_CAST") - private fun decodeHeap( - model: KModel, - addressesMapping: AddressesMapping, - ): UHeapModel { - val resolvedInputFields = mutableMapOf>() - val resolvedInputArrays = mutableMapOf>() - val resolvedInputArrayLengths = mutableMapOf>() - - // Performs decoding from model to concrete UMemoryRegions. - // It's an internal knowledge of initialValue for each type of URegionId, so we use .cast() here. - translatedRegionIds.forEach { - when (it) { - is UInputFieldId<*, *> -> { - val resolved = UMemory1DArray( - regionIdInitialValueProvider.visit(it).cast(), - model, - addressesMapping - ) - resolvedInputFields[it.field as Field] = resolved - } - - is UInputArrayId<*, *> -> { - val resolved = UMemory2DArray( - regionIdInitialValueProvider.visit(it).cast(), - model, - addressesMapping - ) - resolvedInputArrays[it.arrayType as Type] = resolved - } - - is UInputArrayLengthId<*> -> { - val resolved = UMemory1DArray( - regionIdInitialValueProvider.visit(it).cast(), - model, - addressesMapping - ) - resolvedInputArrayLengths[it.arrayType as Type] = resolved - } - } - } - - return UHeapModel( - addressesMapping.getValue(translatedNullRef), - resolvedInputFields, - resolvedInputArrays, - resolvedInputArrayLengths - ) - } - - private fun decodeMocker( - model: KModel, - addressesMapping: AddressesMapping, - ): UIndexedMockModel { - val values = indexedMethodReturnValueToTranslated.replaceUninterpretedConsts(model, addressesMapping) - - return UIndexedMockModel(addressesMapping.getValue(translatedNullRef), values) - } - - /** - * Since expressions from [this] might have the [UAddressSort] and therefore - * they could be uninterpreted constants, we have to replace them with - * corresponding concrete addresses from the [addressesMapping]. - */ - private fun Map>.replaceUninterpretedConsts( - model: KModel, - addressesMapping: AddressesMapping, - ): Map> { - val values = mapValues { (_, expr) -> - val value = model.eval(expr, isComplete = true) - val transformedValue = value.mapAddress(addressesMapping) - - transformedValue - } - - return values - } - -} - -class URegistersStackModel( - private val nullRef: UConcreteHeapRef, - private val registers: Map> -) : URegistersStackEvaluator { - override fun eval( - registerIndex: Int, - sort: Sort, - ): UExpr = registers.getOrDefault(registerIndex, sort.sampleValue().nullAddress(nullRef)).asExpr(sort) -} - -/** - * A model for an indexed mocker that stores mapping - * from mock symbols and invocation indices to expressions. - */ -class UIndexedMockModel( - private val nullRef: UConcreteHeapRef, - private val values: Map, UExpr<*>>, -) : UMockEvaluator { - - override fun eval(symbol: UMockSymbol): UExpr { - require(symbol is UIndexedMethodReturnValue<*, Sort>) - - val sort = symbol.sort - - @Suppress("UNCHECKED_CAST") - val key = symbol.method as Method to symbol.callIndex - - return values.getOrDefault(key, sort.sampleValue().nullAddress(nullRef)).asExpr(sort) - } -} - -/** - * An immutable heap model. Declared as mutable heap for using in regions composition in [UComposer]. Any call to - * modifying operation throws an exception. - * - * Any [UHeapReading] possibly writing to this heap in its [URegionId.instantiate] call actually has empty updates, - * because localization took place, so this heap won't be mutated. - */ -class UHeapModel( - private val nullRef: UConcreteHeapRef, - private val resolvedInputFields: Map>, - private val resolvedInputArrays: Map>, - private val resolvedInputLengths: Map>, -) : USymbolicHeap { - override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr { - // All the expressions in the model are interpreted, therefore, they must - // have concrete addresses. Moreover, the model knows only about input values - // which have addresses less or equal than INITIAL_INPUT_ADDRESS - require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - - @Suppress("UNCHECKED_CAST") - val region = resolvedInputFields.getOrElse(field) { - UMemory1DArray(sort.sampleValue().nullAddress(nullRef)) - } as UMemoryRegion - - return region.read(ref) - } - - override fun readArrayIndex( - ref: UHeapRef, - index: USizeExpr, - arrayType: ArrayType, - sort: Sort, - ): UExpr { - // All the expressions in the model are interpreted, therefore, they must - // have concrete addresses. Moreover, the model knows only about input values - // which have addresses less or equal than INITIAL_INPUT_ADDRESS - require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - - val key = ref to index - - @Suppress("UNCHECKED_CAST") - val region = resolvedInputArrays.getOrElse(arrayType) { - UMemory2DArray(sort.sampleValue().nullAddress(nullRef)) - } as UMemoryRegion - - return region.read(key) - } - - override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType): USizeExpr { - // All the expressions in the model are interpreted, therefore, they must - // have concrete addresses. Moreover, the model knows only about input values - // which have addresses less or equal than INITIAL_INPUT_ADDRESS - require(ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) - - val region = resolvedInputLengths.getOrElse>(arrayType) { - UMemory1DArray(ref.uctx.sizeSort.sampleValue()) - } - - return region.read(ref) - } - - override fun writeField( - ref: UHeapRef, - field: Field, - sort: Sort, - value: UExpr, - guard: UBoolExpr, - ) = error("Illegal operation for a model") - - override fun writeArrayIndex( - ref: UHeapRef, - index: USizeExpr, - type: ArrayType, - sort: Sort, - value: UExpr, - guard: UBoolExpr, - ) = error("Illegal operation for a model") - - override fun writeArrayLength(ref: UHeapRef, size: USizeExpr, arrayType: ArrayType) = - error("Illegal operation for a model") - - override fun memcpy( - srcRef: UHeapRef, - dstRef: UHeapRef, - type: ArrayType, - elementSort: Sort, - fromSrcIdx: USizeExpr, - fromDstIdx: USizeExpr, - toDstIdx: USizeExpr, - guard: UBoolExpr, - ) = error("Illegal operation for a model") - - override fun memset( - ref: UHeapRef, - type: ArrayType, - sort: Sort, - contents: Sequence>, - ) = error("Illegal operation for a model") - - override fun allocate() = error("Illegal operation for a model") - - override fun allocateArray(count: USizeExpr) = error("Illegal operation for a model") - - override fun nullRef(): UConcreteHeapRef = nullRef - - override fun toMutableHeap(): UHeapModel = this -} - -fun UExpr.nullAddress(nullRef: UConcreteHeapRef): UExpr = - if (this == uctx.nullRef) { - nullRef.asExpr(sort) - } else { - this - } - -/** - * If [this] value is an instance of address expression, returns - * an expression with a corresponding concrete address, otherwise - * returns [this] unchanched. - */ -fun UExpr.mapAddress( - addressesMapping: AddressesMapping, -): UExpr = if (sort == uctx.addressSort) { - addressesMapping.getValue(asExpr(uctx.addressSort)).asExpr(sort) -} else { - this -} \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt b/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt index 3b1aad667e..27df1e9a1d 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ModelRegions.kt @@ -7,13 +7,12 @@ import org.ksmt.expr.* import org.ksmt.solver.KModel import org.ksmt.sort.KArray2Sort import org.ksmt.sort.KArraySort -import org.ksmt.utils.sampleValue /** * A specific [UMemoryRegion] for one-dimensional regions generalized by a single expression of a [KeySort]. */ -class UMemory1DArray( +class UMemory1DArray internal constructor( private val values: PersistentMap, UExpr>, private val constValue: UExpr, ) : UMemoryRegion, Sort> { @@ -78,8 +77,7 @@ class UMemory1DArray( valueCopy as KArrayConst, Sort> val constValue = valueCopy.value.mapAddress(mapping) - val values = stores - return UMemory1DArray(values.toPersistentMap(), constValue) + return UMemory1DArray(stores.toPersistentMap(), constValue) } } } @@ -88,7 +86,7 @@ class UMemory1DArray( * A specific [UMemoryRegion] for two-dimensional regions generalized by a pair * of two expressions with [Key1Sort] and [Key2Sort] sorts. */ -class UMemory2DArray( +class UMemory2DArray internal constructor( val values: PersistentMap, UExpr>, UExpr>, val constValue: UExpr, ) : UMemoryRegion, KExpr>, Sort> { @@ -152,8 +150,7 @@ class UMemory2DArray( valueCopy as KArrayConst, Sort> val constValue = valueCopy.value.mapAddress(mapping) - val values = stores - return UMemory2DArray(values.toPersistentMap(), constValue) + return UMemory2DArray(stores.toPersistentMap(), constValue) } } } \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/Solver.kt b/usvm-core/src/main/kotlin/org/usvm/Solver.kt index 883ded2d30..1ff139ea50 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Solver.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Solver.kt @@ -46,9 +46,9 @@ open class USolverBase( } } val model = solver.model().detach() + val uModel = decoder.decode(memory, model) solver.pop() - val uModel = decoder.decode(memory, model) return USolverSat(uModel) } } diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index fb92758379..987ce7d98b 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -452,7 +452,7 @@ internal class CompositionTest { fun testHeapRefEq() = with(ctx) { val concreteNull = ctx.mkConcreteHeapRef(NULL_ADDRESS) val stackModel = - URegistersStackModel(concreteNull, mapOf(0 to mkConcreteHeapRef(-1), 1 to mkConcreteHeapRef(-2))) + URegistersStackEagerModel(concreteNull, mapOf(0 to mkConcreteHeapRef(-1), 1 to mkConcreteHeapRef(-2))) val composer = UComposer(this, stackModel, mockk(), mockk(), mockk()) @@ -465,7 +465,7 @@ internal class CompositionTest { @Test fun testHeapRefNullAddress() = with(ctx) { val concreteNull = ctx.mkConcreteHeapRef(NULL_ADDRESS) - val stackModel = URegistersStackModel(concreteNull, mapOf(0 to mkConcreteHeapRef(0))) + val stackModel = URegistersStackEagerModel(concreteNull, mapOf(0 to mkConcreteHeapRef(0))) val heapEvaluator: UReadOnlySymbolicHeap = mockk() every { heapEvaluator.nullRef() } returns mkConcreteHeapRef(NULL_ADDRESS) @@ -491,7 +491,7 @@ internal class CompositionTest { regionHeap.writeArrayIndex(composedSymbolicHeapRef, mkBv(3), arrayType, bv32Sort, mkBv(1337), trueExpr) - val stackModel = URegistersStackModel( + val stackModel = URegistersStackEagerModel( concreteNull, mapOf( 0 to composedSymbolicHeapRef, 1 to composedSymbolicHeapRef, @@ -544,7 +544,7 @@ internal class CompositionTest { every { heapEvaluator.nullRef() } returns concreteNull - val stackModel = URegistersStackModel(concreteNull, mapOf(0 to mkBv(0), 1 to mkBv(0), 2 to mkBv(2))) + val stackModel = URegistersStackEagerModel(concreteNull, mapOf(0 to mkBv(0), 1 to mkBv(0), 2 to mkBv(2))) val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) diff --git a/usvm-core/src/test/kotlin/org/usvm/ModelCompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/ModelCompositionTest.kt index 6b001188a2..f043947387 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ModelCompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ModelCompositionTest.kt @@ -18,14 +18,14 @@ class ModelCompositionTest { @Test fun testComposeAllocatedArray() = with(ctx) { - val heapEvaluator = UHeapModel( + val heapEvaluator = UHeapEagerModel( concreteNull, mapOf(), mapOf(), mapOf(), ) - val stackModel = URegistersStackModel(concreteNull, mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) + val stackModel = URegistersStackEagerModel(concreteNull, mapOf(0 to ctx.mkBv(0), 1 to ctx.mkBv(0), 2 to ctx.mkBv(2))) val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) @@ -45,7 +45,7 @@ class ModelCompositionTest { val arrayType = mockk() val composedSymbolicHeapRef = ctx.mkConcreteHeapRef(-1) val inputArray = UMemory2DArray(persistentMapOf((composedSymbolicHeapRef to mkBv(0)) to mkBv(1)), mkBv(0)) - val heapEvaluator = UHeapModel( + val heapEvaluator = UHeapEagerModel( concreteNull, mapOf(), mapOf(arrayType to inputArray), @@ -53,7 +53,7 @@ class ModelCompositionTest { ) val stackModel = - URegistersStackModel(concreteNull, mapOf(0 to composedSymbolicHeapRef, 1 to mkBv(0))) + URegistersStackEagerModel(concreteNull, mapOf(0 to composedSymbolicHeapRef, 1 to mkBv(0))) val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) val symbolicRef = mkRegisterReading(0, addressSort) @@ -88,14 +88,14 @@ class ModelCompositionTest { val arrayType = mockk() val inputLength = UMemory1DArray(persistentMapOf(composedRef0 to mkBv(42)), mkBv(0)) - val heapEvaluator = UHeapModel( + val heapEvaluator = UHeapEagerModel( concreteNull, mapOf(), mapOf(), mapOf(arrayType to inputLength), ) - val stackModel = URegistersStackModel( + val stackModel = URegistersStackEagerModel( concreteNull, mapOf( 0 to composedRef0, @@ -131,14 +131,14 @@ class ModelCompositionTest { val field = mockk() val inputField = UMemory1DArray(persistentMapOf(composedRef0 to composedRef0), concreteNull) - val heapEvaluator = UHeapModel( + val heapEvaluator = UHeapEagerModel( concreteNull, mapOf(field to inputField), mapOf(), mapOf(), ) - val stackModel = URegistersStackModel( + val stackModel = URegistersStackEagerModel( concreteNull, mapOf( 0 to composedRef0, diff --git a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt index fdeed716b5..17f5627d09 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt @@ -19,7 +19,7 @@ class ModelDecodingTest { @BeforeEach fun initializeContext() { ctx = UContext() - val (translator, decoder) = buildDefaultTranslatorAndDecoder(ctx) + val (translator, decoder) = buildTranslatorAndLazyDecoder(ctx) solver = USolverBase(ctx, KZ3Solver(ctx), translator, decoder) stack = URegistersStack(ctx) From 4542884a22dddbc06b84b0a784b6d8d1ce47a887 Mon Sep 17 00:00:00 2001 From: Sergey Pospelov Date: Wed, 19 Apr 2023 13:06:04 +0300 Subject: [PATCH 28/28] Fix: minor and docs --- .../src/main/kotlin/org/usvm/EagerModels.kt | 12 ++++++-- .../main/kotlin/org/usvm/LazyModelDecoder.kt | 24 ++++------------ .../src/main/kotlin/org/usvm/LazyModels.kt | 28 ++++++++++++++----- .../test/kotlin/org/usvm/ModelDecodingTest.kt | 26 +++++++++++++++++ 4 files changed, 62 insertions(+), 28 deletions(-) diff --git a/usvm-core/src/main/kotlin/org/usvm/EagerModels.kt b/usvm-core/src/main/kotlin/org/usvm/EagerModels.kt index 0febb19635..9fac171b7b 100644 --- a/usvm-core/src/main/kotlin/org/usvm/EagerModels.kt +++ b/usvm-core/src/main/kotlin/org/usvm/EagerModels.kt @@ -3,6 +3,10 @@ package org.usvm import org.ksmt.utils.asExpr import org.ksmt.utils.sampleValue +/** + * An eager model for registers that stores mapping + * from mock symbols to evaluated expressions. + */ class URegistersStackEagerModel( private val nullRef: UConcreteHeapRef, private val registers: Map> @@ -14,7 +18,7 @@ class URegistersStackEagerModel( } /** - * A model for an indexed mocker that stores mapping + * An eager model for an indexed mocker that stores mapping * from mock symbols and invocation indices to expressions. */ class UIndexedMockEagerModel( @@ -35,11 +39,13 @@ class UIndexedMockEagerModel( } /** - * An immutable heap model. Declared as mutable heap for using in regions composition in [UComposer]. Any call to + * An eager immutable heap model. + * + * Declared as mutable heap for using in regions composition in [UComposer]. Any call to * modifying operation throws an exception. * * Any [UHeapReading] possibly writing to this heap in its [URegionId.instantiate] call actually has empty updates, - * because localization took place, so this heap won't be mutated. + * because localization happened, so this heap won't be mutated. */ class UHeapEagerModel( private val nullRef: UConcreteHeapRef, diff --git a/usvm-core/src/main/kotlin/org/usvm/LazyModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/LazyModelDecoder.kt index be6cf69be6..d290132600 100644 --- a/usvm-core/src/main/kotlin/org/usvm/LazyModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/LazyModelDecoder.kt @@ -3,7 +3,6 @@ package org.usvm import org.ksmt.expr.KExpr import org.ksmt.solver.KModel import org.ksmt.sort.KUninterpretedSort -import org.ksmt.utils.asExpr import org.usvm.UAddressCounter.Companion.INITIAL_INPUT_ADDRESS import org.usvm.UAddressCounter.Companion.NULL_ADDRESS @@ -37,7 +36,7 @@ typealias AddressesMapping = Map, UConcreteHeapRef> /** - * Base decoder suitable for decoding [KModel] to [UModelBase]. It can't be reused between different root methods, + * A lazy decoder suitable for decoding [KModel] to [UModelBase]. It can't be reused between different root methods, * because of a matched translator caches. * * Passed parameters updates on the fly in a matched translator, so they are mutable in fact. @@ -86,6 +85,11 @@ open class ULazyModelDecoder( return result } + /** + * Decodes a [model] from a [memory] to a [UModelBase]. + * + * @param model should be detached. + */ override fun decode( memory: UMemoryBase, model: KModel, @@ -118,9 +122,6 @@ open class ULazyModelDecoder( addressesMapping.getValue(translatedNullRef), addressesMapping, regionIdToInitialValue, - mutableMapOf(), - mutableMapOf(), - mutableMapOf() ) private fun decodeMocker( @@ -133,16 +134,3 @@ open class ULazyModelDecoder( indexedMethodReturnValueToTranslated ) } - -/** - * If [this] value is an instance of address expression, returns - * an expression with a corresponding concrete address, otherwise - * returns [this] unchanched. - */ -fun UExpr.mapAddress( - addressesMapping: AddressesMapping, -): UExpr = if (sort == uctx.addressSort) { - addressesMapping.getValue(asExpr(uctx.addressSort)).asExpr(sort) -} else { - this -} \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/LazyModels.kt b/usvm-core/src/main/kotlin/org/usvm/LazyModels.kt index e55f9dad32..3a69d235f4 100644 --- a/usvm-core/src/main/kotlin/org/usvm/LazyModels.kt +++ b/usvm-core/src/main/kotlin/org/usvm/LazyModels.kt @@ -70,15 +70,16 @@ class ULazyIndexedMockModel( /** * * A lazy immutable heap model. Firstly, searches for decoded [UMemoryRegion], decodes it from [model] if not found, - * then evaluates a value from it. + * secondly, evaluates a value from it. * * Declared as mutable heap for using in regions composition in [UComposer]. Any call to * modifying operation throws an exception. * * Any [UHeapReading] possibly writing to this heap in its [URegionId.instantiate] call actually has empty updates, - * because localization took place, so this heap won't be mutated. + * because localization happened, so this heap won't be mutated. * - * @param regionIdToInitialValue [URegionId] to initial values cache. Need to perform decoding from [model]. + * @param regionIdToInitialValue mapping from [URegionId] to initial values. We decode memory regions + * using this cache. * @param model has to be detached. */ class ULazyHeapModel( @@ -86,10 +87,10 @@ class ULazyHeapModel( private val nullRef: UConcreteHeapRef, private val addressesMapping: AddressesMapping, private val regionIdToInitialValue: Map, KExpr<*>>, - private val resolvedInputFields: MutableMap>, - private val resolvedInputArrays: MutableMap>, - private val resolvedInputLengths: MutableMap>, ) : USymbolicHeap { + private val resolvedInputFields = mutableMapOf>() + private val resolvedInputArrays = mutableMapOf>() + private val resolvedInputLengths = mutableMapOf>() override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr { // All the expressions in the model are interpreted, therefore, they must // have concrete addresses. Moreover, the model knows only about input values @@ -213,4 +214,17 @@ fun UExpr.makeNullRefConcrete(conreteNullRef: UConcreteHeapRef): conreteNullRef.asExpr(sort) } else { this - } \ No newline at end of file + } + +/** + * If [this] value is an instance of address expression, returns + * an expression with a corresponding concrete address, otherwise + * returns [this] unchanched. + */ +fun UExpr.mapAddress( + addressesMapping: AddressesMapping, +): UExpr = if (sort == uctx.addressSort) { + addressesMapping.getValue(asExpr(uctx.addressSort)).asExpr(sort) +} else { + this +} \ No newline at end of file diff --git a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt index 17f5627d09..231a879ef9 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ModelDecodingTest.kt @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Assertions.assertSame import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.ksmt.solver.z3.KZ3Solver +import kotlin.test.assertFalse import kotlin.test.assertIs class ModelDecodingTest { @@ -143,4 +144,29 @@ class ModelDecodingTest { assertSame(model.eval(symbolicRef1), model.eval(symbolicRef2)) } + + @Test + fun testLoopedWritingsToArray() = with(ctx) { + val array = mockk() + + val symbolicRef0 = stack.readRegister(0, addressSort) + val symbolicRef1 = stack.readRegister(1, addressSort) + val symbolicRef2 = stack.readRegister(2, addressSort) + + val concreteIdx = mkBv(3) + + heap.writeArrayIndex(symbolicRef0, concreteIdx, array, addressSort, symbolicRef1, trueExpr) + heap.writeArrayIndex(symbolicRef1, concreteIdx, array, addressSort, symbolicRef2, trueExpr) + heap.writeArrayIndex(symbolicRef2, concreteIdx, array, addressSort, symbolicRef0, trueExpr) + + val readedRef = heap.readArrayIndex(symbolicRef0, concreteIdx, array, addressSort) + val pc = (symbolicRef0 neq nullRef) and (symbolicRef1 neq nullRef) and (symbolicRef2 neq nullRef) and + (readedRef neq symbolicRef1) and (symbolicRef0 eq symbolicRef1) + + val status = solver.check(memory, UPathConstraintsSet(pc)) + val model = assertIs>>(status).model + + assertSame(falseExpr, model.eval(symbolicRef2 eq symbolicRef0)) + assertSame(model.eval(readedRef), model.eval(symbolicRef2)) + } }