diff --git a/docs/meeting-minutes/2023-08-18.md b/docs/meeting-minutes/2023-08-18.md new file mode 100644 index 000000000..7c1114249 --- /dev/null +++ b/docs/meeting-minutes/2023-08-18.md @@ -0,0 +1,18 @@ +# TODOs for refactoring + +- Elaborate on API +- Make everything internal +- [+] Implement model decoding: use interfaces for MemoryRegions (arrays, field, etc.) +- Reimplement map merging into UMapRegions +- [+] Add (exceptional) `URegisterRef` into `UMemory` +- `Regions.kt`: implement unions? +- Implement symbolic sets: `memory/collections/SymbolicCollectionIds.kt`. Encoding/decoding? +- Include element remove information into `SymbolicSetRegionBuilder`. For symbolic set do not traverse updates. +- [+] Interpreter uses new API +- [+] Think about getting rid of unchecked casts in ranged update adapters +- [+] Think about moving `contextMemory` from each collectionId to `USymbolicCollectionId` +- [+] Remove all commented out code +- [+] Make everything compilable +- [+] Rebase onto new master +- [+] Repair tests +- [+] collection id equals, hash \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/Composition.kt b/usvm-core/src/main/kotlin/org/usvm/Composition.kt index 4342c1fae..eb47cddd3 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Composition.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Composition.kt @@ -1,27 +1,31 @@ package org.usvm -import io.ksmt.expr.KExpr -import io.ksmt.expr.KIteExpr -import io.ksmt.sort.KSort -import org.usvm.constraints.UTypeEvaluator -import org.usvm.memory.UReadOnlySymbolicHeap -import org.usvm.memory.URegionId -import org.usvm.memory.URegistersStackEvaluator +import org.usvm.collection.array.UAllocatedArrayReading +import org.usvm.collection.array.UInputArrayReading +import org.usvm.collection.array.length.UInputArrayLengthReading +import org.usvm.collection.field.UInputFieldReading +import org.usvm.collection.map.length.UInputMapLengthReading +import org.usvm.collection.map.primitive.UAllocatedMapReading +import org.usvm.collection.map.primitive.UInputMapReading +import org.usvm.collection.map.ref.UAllocatedRefMapWithInputKeysReading +import org.usvm.collection.map.ref.UInputRefMapWithAllocatedKeysReading +import org.usvm.collection.map.ref.UInputRefMapWithInputKeysReading +import org.usvm.memory.UReadOnlyMemory +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.USymbolicCollectionId +import org.usvm.util.Region @Suppress("MemberVisibilityCanBePrivate") -open class UComposer( +open class UComposer( ctx: UContext, - internal val stackEvaluator: URegistersStackEvaluator, - internal val heapEvaluator: UReadOnlySymbolicHeap, - internal val typeEvaluator: UTypeEvaluator, - internal val mockEvaluator: UMockEvaluator, -) : UExprTransformer(ctx) { + internal val memory: UReadOnlyMemory +) : UExprTransformer(ctx) { open fun compose(expr: UExpr): UExpr = apply(expr) override fun transform(expr: USymbol): UExpr = error("You must override `transform` function in org.usvm.UComposer for ${expr::class}") - override fun transform(expr: KIteExpr): KExpr = + override fun transform(expr: UIteExpr): UExpr = transformExprAfterTransformed(expr, expr.condition) { condition -> when { condition.isTrue -> apply(expr.trueBranch) @@ -32,9 +36,9 @@ open class UComposer( override fun transform( expr: URegisterReading, - ): UExpr = with(expr) { stackEvaluator.readRegister(idx, sort) } + ): UExpr = with(expr) { memory.stack.readRegister(idx, sort) } - override fun transform(expr: UHeapReading<*, *, *>): UExpr = + override fun transform(expr: UCollectionReading<*, *, *>): UExpr = error("You must override `transform` function in org.usvm.UComposer for ${expr::class}") override fun transform(expr: UMockSymbol): UExpr = @@ -42,40 +46,72 @@ open class UComposer( override fun transform( expr: UIndexedMethodReturnValue, - ): UExpr = mockEvaluator.eval(expr) + ): UExpr = memory.mocker.eval(expr) override fun transform(expr: UIsSubtypeExpr): UBoolExpr = transformExprAfterTransformed(expr, expr.ref) { ref -> - typeEvaluator.evalIsSubtype(ref, expr.supertype) + memory.types.evalIsSubtype(ref, expr.supertype) } override fun transform(expr: UIsSupertypeExpr): UBoolExpr = transformExprAfterTransformed(expr, expr.ref) { ref -> - typeEvaluator.evalIsSupertype(ref, expr.subtype) + memory.types.evalIsSupertype(ref, expr.subtype) } - fun , Key, Sort : USort> transformHeapReading( - expr: UHeapReading, + fun , Key, Sort : USort> transformCollectionReading( + expr: UCollectionReading, key: Key, ): UExpr = with(expr) { - val mappedRegion = region.map(this@UComposer) - val mappedKey = mappedRegion.regionId.keyMapper(this@UComposer)(key) - mappedRegion.read(mappedKey) + val mappedCollectionId = collection.collectionId.map(this@UComposer) + val mappedKey = mappedCollectionId.keyMapper(this@UComposer)(key) + val decomposedKey = mappedCollectionId.rebindKey(mappedKey) + if (decomposedKey != null) { + @Suppress("UNCHECKED_CAST") + // I'm terribly sorry to do this cast, but it's impossible to do it type safe way :( + val mappedCollection = + collection.mapTo(this@UComposer, decomposedKey.collectionId) as USymbolicCollection<*, Any?, Sort> + return mappedCollection.read(decomposedKey.key) + } + val mappedCollection = collection.mapTo(this@UComposer, mappedCollectionId) + return mappedCollection.read(mappedKey) } override fun transform(expr: UInputArrayLengthReading): USizeExpr = - transformHeapReading(expr, expr.address) + transformCollectionReading(expr, expr.address) override fun transform(expr: UInputArrayReading): UExpr = - transformHeapReading(expr, expr.address to expr.index) + transformCollectionReading(expr, expr.address to expr.index) override fun transform(expr: UAllocatedArrayReading): UExpr = - transformHeapReading(expr, expr.index) + transformCollectionReading(expr, expr.index) + + override fun transform(expr: UInputFieldReading): UExpr = + transformCollectionReading(expr, expr.address) + + override fun > transform( + expr: UAllocatedMapReading + ): UExpr = transformCollectionReading(expr, expr.key) + + override fun > transform( + expr: UInputMapReading + ): UExpr = transformCollectionReading(expr, expr.address to expr.key) + + override fun transform( + expr: UAllocatedRefMapWithInputKeysReading + ): UExpr = transformCollectionReading(expr, expr.keyRef) + + override fun transform( + expr: UInputRefMapWithAllocatedKeysReading + ): UExpr = transformCollectionReading(expr, expr.mapRef) + + override fun transform( + expr: UInputRefMapWithInputKeysReading + ): UExpr = transformCollectionReading(expr, expr.mapRef to expr.keyRef) - override fun transform(expr: UInputFieldReading): UExpr = - transformHeapReading(expr, expr.address) + override fun transform(expr: UInputMapLengthReading): USizeExpr = + transformCollectionReading(expr, expr.address) override fun transform(expr: UConcreteHeapRef): UExpr = expr - override fun transform(expr: UNullRef): UExpr = heapEvaluator.nullRef() + override fun transform(expr: UNullRef): UExpr = memory.nullRef() } diff --git a/usvm-core/src/main/kotlin/org/usvm/Context.kt b/usvm-core/src/main/kotlin/org/usvm/Context.kt index c89ffe0b6..6e42e6931 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Context.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Context.kt @@ -12,17 +12,34 @@ import io.ksmt.utils.DefaultValueSampler import io.ksmt.utils.asExpr import io.ksmt.utils.cast import io.ksmt.utils.uncheckedCast -import org.usvm.memory.UAllocatedArrayRegion -import org.usvm.memory.UInputArrayLengthRegion -import org.usvm.memory.UInputArrayRegion -import org.usvm.memory.UInputFieldRegion +import org.usvm.collection.array.UAllocatedArray +import org.usvm.collection.array.UAllocatedArrayReading +import org.usvm.collection.array.UInputArray +import org.usvm.collection.array.UInputArrayReading +import org.usvm.collection.array.length.UInputArrayLengthReading +import org.usvm.collection.array.length.UInputArrayLengths +import org.usvm.collection.field.UInputFieldReading +import org.usvm.collection.field.UInputFields +import org.usvm.collection.map.length.UInputMapLengthCollection +import org.usvm.collection.map.length.UInputMapLengthReading +import org.usvm.collection.map.primitive.UAllocatedMap +import org.usvm.collection.map.primitive.UAllocatedMapReading +import org.usvm.collection.map.primitive.UInputMap +import org.usvm.collection.map.primitive.UInputMapReading +import org.usvm.collection.map.ref.UAllocatedRefMapWithInputKeys +import org.usvm.collection.map.ref.UAllocatedRefMapWithInputKeysReading +import org.usvm.collection.map.ref.UInputRefMap +import org.usvm.collection.map.ref.UInputRefMapWithAllocatedKeys +import org.usvm.collection.map.ref.UInputRefMapWithAllocatedKeysReading +import org.usvm.collection.map.ref.UInputRefMapWithInputKeysReading import org.usvm.memory.splitUHeapRef import org.usvm.solver.USolverBase import org.usvm.types.UTypeSystem +import org.usvm.util.Region @Suppress("LeakingThis") open class UContext( - components: UComponents<*, *, *>, + components: UComponents<*>, operationMode: OperationMode = OperationMode.CONCURRENT, astManagementMode: AstManagementMode = AstManagementMode.GC, simplificationMode: SimplificationMode = SimplificationMode.SIMPLIFY, @@ -41,8 +58,8 @@ open class UContext( } @Suppress("UNCHECKED_CAST") - fun solver(): USolverBase = - this.solver as USolverBase + fun solver(): USolverBase = + this.solver as USolverBase @Suppress("UNCHECKED_CAST") fun typeSystem(): UTypeSystem = @@ -102,14 +119,14 @@ open class UContext( concreteRefsRhs.forEach { (concreteRefRhs, guardRhs) -> val guardLhs = concreteRefLhsToGuard.getOrDefault(concreteRefRhs.address, falseExpr) - // mkAnd instead of mkAndNoFlat here is OK + // mkAnd instead of mkAnd with flat=false here is OK val conjunct = mkAnd(guardLhs, guardRhs) conjuncts += conjunct } if (symbolicRefLhs != null && symbolicRefRhs != null) { val refsEq = super.mkEq(symbolicRefLhs.expr, symbolicRefRhs.expr, order = true) - // mkAnd instead of mkAndNoFlat here is OK + // mkAnd instead of mkAnd with flat=false here is OK val conjunct = mkAnd(symbolicRefLhs.guard, symbolicRefRhs.guard, refsEq) conjuncts += conjunct } @@ -132,7 +149,7 @@ open class UContext( private val inputFieldReadingCache = mkAstInterner>() fun mkInputFieldReading( - region: UInputFieldRegion, + region: UInputFields, address: UHeapRef, ): UInputFieldReading = inputFieldReadingCache.createIfContextActive { UInputFieldReading(this, region, address) @@ -141,7 +158,7 @@ open class UContext( private val allocatedArrayReadingCache = mkAstInterner>() fun mkAllocatedArrayReading( - region: UAllocatedArrayRegion, + region: UAllocatedArray, index: USizeExpr, ): UAllocatedArrayReading = allocatedArrayReadingCache.createIfContextActive { UAllocatedArrayReading(this, region, index) @@ -150,7 +167,7 @@ open class UContext( private val inputArrayReadingCache = mkAstInterner>() fun mkInputArrayReading( - region: UInputArrayRegion, + region: UInputArray, address: UHeapRef, index: USizeExpr, ): UInputArrayReading = inputArrayReadingCache.createIfContextActive { @@ -160,12 +177,77 @@ open class UContext( private val inputArrayLengthReadingCache = mkAstInterner>() fun mkInputArrayLengthReading( - region: UInputArrayLengthRegion, + region: UInputArrayLengths, address: UHeapRef, ): UInputArrayLengthReading = inputArrayLengthReadingCache.createIfContextActive { UInputArrayLengthReading(this, region, address) }.cast() + private val allocatedSymbolicMapReadingCache = mkAstInterner>() + + fun > mkAllocatedMapReading( + region: UAllocatedMap, + key: UExpr + ): UAllocatedMapReading = + allocatedSymbolicMapReadingCache.createIfContextActive { + UAllocatedMapReading(this, region, key) + }.cast() + + private val inputSymbolicMapReadingCache = mkAstInterner>() + + fun , Sort : USort> mkInputMapReading( + region: UInputMap, + address: UHeapRef, + key: UExpr + ): UInputMapReading = + inputSymbolicMapReadingCache.createIfContextActive { + UInputMapReading(this, region, address, key) + }.cast() + + private val allocatedSymbolicRefMapWithInputKeysReadingCache = + mkAstInterner>() + + fun mkAllocatedRefMapWithInputKeysReading( + region: UAllocatedRefMapWithInputKeys, + keyRef: UHeapRef + ): UAllocatedRefMapWithInputKeysReading = + allocatedSymbolicRefMapWithInputKeysReadingCache.createIfContextActive { + UAllocatedRefMapWithInputKeysReading(this, region, keyRef) + }.cast() + + private val inputSymbolicRefMapWithAllocatedKeysReadingCache = + mkAstInterner>() + + fun mkInputRefMapWithAllocatedKeysReading( + region: UInputRefMapWithAllocatedKeys, + mapRef: UHeapRef + ): UInputRefMapWithAllocatedKeysReading = + inputSymbolicRefMapWithAllocatedKeysReadingCache.createIfContextActive { + UInputRefMapWithAllocatedKeysReading(this, region, mapRef) + }.cast() + + private val inputSymbolicRefMapWithInputKeysReadingCache = + mkAstInterner>() + + fun mkInputRefMapWithInputKeysReading( + region: UInputRefMap, + mapRef: UHeapRef, + keyRef: UHeapRef + ): UInputRefMapWithInputKeysReading = + inputSymbolicRefMapWithInputKeysReadingCache.createIfContextActive { + UInputRefMapWithInputKeysReading(this, region, mapRef, keyRef) + }.cast() + + private val inputSymbolicMapLengthReadingCache = mkAstInterner>() + + fun mkInputMapLengthReading( + region: UInputMapLengthCollection, + address: UHeapRef + ): UInputMapLengthReading = + inputSymbolicMapLengthReadingCache.createIfContextActive { + UInputMapLengthReading(this, region, address) + }.cast() + private val indexedMethodReturnValueCache = mkAstInterner>() fun mkIndexedMethodReturnValue( @@ -200,8 +282,8 @@ open class UContext( // Type hack to be able to intern the initial location for inheritors. private val initialLocation = RootNode() - fun , Statement> mkInitialLocation() - : PathsTrieNode = initialLocation.uncheckedCast() + fun , Statement> mkInitialLocation() + : PathsTrieNode = initialLocation.uncheckedCast() fun mkUValueSampler(): KSortVisitor> { return UValueSampler(this) @@ -217,6 +299,17 @@ open class UContext( super.visit(sort) } } + + inline fun mkIte( + condition: KExpr, + trueBranch: () -> KExpr, + falseBranch: () -> KExpr + ): KExpr = + when (condition) { + is UTrue -> trueBranch() + is UFalse -> falseBranch() + else -> mkIte(condition, trueBranch(), falseBranch()) + } } diff --git a/usvm-core/src/main/kotlin/org/usvm/ExprTransformer.kt b/usvm-core/src/main/kotlin/org/usvm/ExprTransformer.kt index affa0e32f..c3a48c742 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ExprTransformer.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ExprTransformer.kt @@ -2,19 +2,51 @@ package org.usvm import io.ksmt.expr.transformer.KNonRecursiveTransformer import io.ksmt.expr.transformer.KTransformer +import org.usvm.collection.array.UAllocatedArrayReading +import org.usvm.collection.array.UInputArrayReading +import org.usvm.collection.array.length.UInputArrayLengthReading +import org.usvm.collection.field.UInputFieldReading +import org.usvm.collection.map.length.UInputMapLengthReading +import org.usvm.collection.map.primitive.UAllocatedMapReading +import org.usvm.collection.map.primitive.UInputMapReading +import org.usvm.collection.map.ref.UAllocatedRefMapWithInputKeysReading +import org.usvm.collection.map.ref.UInputRefMapWithAllocatedKeysReading +import org.usvm.collection.map.ref.UInputRefMapWithInputKeysReading +import org.usvm.util.Region -interface UTransformer : KTransformer { +interface UTransformer : KTransformer { fun transform(expr: USymbol): UExpr fun transform(expr: URegisterReading): UExpr - fun transform(expr: UHeapReading<*, *, *>): UExpr - fun transform(expr: UInputFieldReading): UExpr + fun transform(expr: UCollectionReading<*, *, *>): UExpr + + fun transform(expr: UInputFieldReading): UExpr + fun transform(expr: UAllocatedArrayReading): UExpr + fun transform(expr: UInputArrayReading): UExpr + fun transform(expr: UInputArrayLengthReading): USizeExpr + fun > transform( + expr: UAllocatedMapReading + ): UExpr + + fun > transform( + expr: UInputMapReading + ): UExpr + + fun transform(expr: UAllocatedRefMapWithInputKeysReading): UExpr + + fun transform(expr: UInputRefMapWithAllocatedKeysReading): UExpr + + fun transform(expr: UInputRefMapWithInputKeysReading): UExpr + + fun transform(expr: UInputMapLengthReading): USizeExpr + fun transform(expr: UMockSymbol): UExpr + fun transform(expr: UIndexedMethodReturnValue): UExpr fun transform(expr: UIsSubtypeExpr): UBoolExpr @@ -26,6 +58,9 @@ interface UTransformer : KTransformer { fun transform(expr: UNullRef): UExpr } -abstract class UExprTransformer( +abstract class UExprTransformer( ctx: UContext -) : KNonRecursiveTransformer(ctx), UTransformer +) : KNonRecursiveTransformer(ctx), UTransformer + +@Suppress("UNCHECKED_CAST") +fun UTransformer<*>.asTypedTransformer() = this as UTransformer diff --git a/usvm-core/src/main/kotlin/org/usvm/Expressions.kt b/usvm-core/src/main/kotlin/org/usvm/Expressions.kt index a6a195c5e..dfd4c7126 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Expressions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Expressions.kt @@ -24,17 +24,8 @@ import io.ksmt.sort.KBvSort import io.ksmt.sort.KFpSort import io.ksmt.sort.KSort import io.ksmt.sort.KUninterpretedSort -import org.usvm.memory.UAllocatedArrayId -import org.usvm.memory.UAllocatedArrayRegion -import org.usvm.memory.UInputArrayId -import org.usvm.memory.UInputArrayLengthId -import org.usvm.memory.UInputArrayLengthRegion -import org.usvm.memory.UInputArrayRegion -import org.usvm.memory.UInputFieldId -import org.usvm.memory.UInputFieldRegion -import org.usvm.memory.URegionId -import org.usvm.memory.USymbolicArrayIndex -import org.usvm.memory.USymbolicMemoryRegion +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.USymbolicCollectionId //region KSMT aliases @@ -63,7 +54,7 @@ typealias UConcreteSize = KBitVec32Value typealias UAddressSort = KUninterpretedSort -typealias UIndexType = Int +typealias USizeType = Int //endregion @@ -105,7 +96,7 @@ class UConcreteHeapRef internal constructor( override val sort: UAddressSort = ctx.addressSort override fun accept(transformer: KTransformerBase): KExpr { - require(transformer is UTransformer<*, *>) { "Expected a UTransformer, but got: $transformer" } + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } return transformer.transform(this) } @@ -125,7 +116,7 @@ class UNullRef internal constructor( get() = uctx.addressSort override fun accept(transformer: KTransformerBase): KExpr { - require(transformer is UTransformer<*, *>) { "Expected a UTransformer, but got: $transformer" } + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } return transformer.transform(this) } @@ -161,27 +152,6 @@ const val INITIAL_INPUT_ADDRESS = NULL_ADDRESS - 1 const val INITIAL_CONCRETE_ADDRESS = NULL_ADDRESS + 1 -//endregion - -//region LValues -open class ULValue(val sort: USort) - -class URegisterLValue(sort: USort, val idx: Int) : ULValue(sort) - -class UFieldLValue(fieldSort: USort, val ref: UHeapRef, val field: Field) : ULValue(fieldSort) - -class UArrayIndexLValue( - cellSort: USort, - val ref: UHeapRef, - val index: USizeExpr, - val arrayType: ArrayType, -) : ULValue(cellSort) - -class UArrayLengthLValue( - val ref: UHeapRef, - val arrayType: ArrayType, -) : ULValue(ref.uctx.sizeSort) - //endregion //region Read Expressions @@ -192,7 +162,7 @@ class URegisterReading internal constructor( override val sort: Sort, ) : USymbol(ctx) { override fun accept(transformer: KTransformerBase): KExpr { - require(transformer is UTransformer<*, *>) { "Expected a UTransformer, but got: $transformer" } + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } return transformer.transform(this) } @@ -205,133 +175,11 @@ class URegisterReading internal constructor( } } -abstract class UHeapReading, Key, Sort : USort>( +abstract class UCollectionReading, Key, Sort : USort>( ctx: UContext, - val region: USymbolicMemoryRegion, + val collection: USymbolicCollection ) : USymbol(ctx) { - override val sort: Sort get() = region.sort -} - -class UInputFieldReading internal constructor( - ctx: UContext, - region: UInputFieldRegion, - val address: UHeapRef, -) : UHeapReading, UHeapRef, Sort>(ctx, region) { - init { - require(address !is UNullRef) - } - - @Suppress("UNCHECKED_CAST") - override fun accept(transformer: KTransformerBase): KExpr { - require(transformer is UTransformer<*, *>) { "Expected a UTransformer, but got: $transformer" } - // An unchecked cast here it to be able to choose the right overload from UTransformer - return (transformer as UTransformer).transform(this) - } - - override fun internEquals(other: Any): Boolean = structurallyEqual(other, { region }, { address }) - - override fun internHashCode(): Int = hash(region, address) - - override fun print(printer: ExpressionPrinter) { - printer.append(region.toString()) - printer.append("[") - printer.append(address) - printer.append("]") - } -} - -class UAllocatedArrayReading internal constructor( - ctx: UContext, - region: UAllocatedArrayRegion, - val index: USizeExpr, -) : UHeapReading, USizeExpr, Sort>(ctx, region) { - @Suppress("UNCHECKED_CAST") - override fun accept(transformer: KTransformerBase): KExpr { - require(transformer is UTransformer<*, *>) { "Expected a UTransformer, but got: $transformer" } - // An unchecked cast here it to be able to choose the right overload from UTransformer - return (transformer as UTransformer<*, ArrayType>).transform(this) - } - - override fun internEquals(other: Any): Boolean = - structurallyEqual( - other, - { region }, - { index }, - ) - - override fun internHashCode(): Int = hash(region, index) - - override fun print(printer: ExpressionPrinter) { - printer.append(region.toString()) - printer.append("[") - printer.append(index) - printer.append("]") - } -} - -class UInputArrayReading internal constructor( - ctx: UContext, - region: UInputArrayRegion, - val address: UHeapRef, - val index: USizeExpr, -) : UHeapReading, USymbolicArrayIndex, Sort>(ctx, region) { - init { - require(address !is UNullRef) - } - - @Suppress("UNCHECKED_CAST") - override fun accept(transformer: KTransformerBase): KExpr { - require(transformer is UTransformer<*, *>) { "Expected a UTransformer, but got: $transformer" } - // An unchecked cast here it to be able to choose the right overload from UTransformer - return (transformer as UTransformer<*, ArrayType>).transform(this) - } - - override fun internEquals(other: Any): Boolean = - structurallyEqual( - other, - { region }, - { address }, - { index }, - ) - - override fun internHashCode(): Int = hash(region, address, index) - - override fun print(printer: ExpressionPrinter) { - printer.append(region.toString()) - printer.append("[") - printer.append(address) - printer.append(", ") - printer.append(index) - printer.append("]") - } -} - -class UInputArrayLengthReading internal constructor( - ctx: UContext, - region: UInputArrayLengthRegion, - val address: UHeapRef, -) : UHeapReading, UHeapRef, USizeSort>(ctx, region) { - init { - require(address !is UNullRef) - } - - @Suppress("UNCHECKED_CAST") - override fun accept(transformer: KTransformerBase): USizeExpr { - require(transformer is UTransformer<*, *>) { "Expected a UTransformer, but got: $transformer" } - // An unchecked cast here it to be able to choose the right overload from UTransformer - return (transformer as UTransformer<*, ArrayType>).transform(this) - } - - override fun internEquals(other: Any): Boolean = structurallyEqual(other, { region }, { address }) - - override fun internHashCode(): Int = hash(region, address) - - override fun print(printer: ExpressionPrinter) { - printer.append(region.toString()) - printer.append("[") - printer.append(address) - printer.append("]") - } + override val sort: Sort get() = collection.sort } //endregion @@ -348,7 +196,7 @@ class UIndexedMethodReturnValue internal constructor( override val sort: Sort, ) : UMockSymbol(ctx, sort) { override fun accept(transformer: KTransformerBase): KExpr { - require(transformer is UTransformer<*, *>) { "Expected a UTransformer, but got: $transformer" } + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } return transformer.transform(this) } @@ -381,11 +229,9 @@ class UIsSubtypeExpr internal constructor( ref: UHeapRef, val supertype: Type, ) : UIsExpr(ctx, ref) { - @Suppress("UNCHECKED_CAST") override fun accept(transformer: KTransformerBase): UBoolExpr { - require(transformer is UTransformer<*, *>) { "Expected a UTransformer, but got: $transformer" } - // An unchecked cast here it to be able to choose the right overload from UTransformer - return (transformer as UTransformer<*, Type>).transform(this) + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } + return transformer.asTypedTransformer().transform(this) } override fun print(printer: ExpressionPrinter) { @@ -406,11 +252,9 @@ class UIsSupertypeExpr internal constructor( ref: UHeapRef, val subtype: Type, ) : UIsExpr(ctx, ref) { - @Suppress("UNCHECKED_CAST") override fun accept(transformer: KTransformerBase): UBoolExpr { - require(transformer is UTransformer<*, *>) { "Expected a UTransformer, but got: $transformer" } - // An unchecked cast here it to be able to choose the right overload from UTransformer - return (transformer as UTransformer<*, Type>).transform(this) + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } + return transformer.asTypedTransformer().transform(this) } override fun print(printer: ExpressionPrinter) { diff --git a/usvm-core/src/main/kotlin/org/usvm/Merging.kt b/usvm-core/src/main/kotlin/org/usvm/Merging.kt index b6de082be..a5ecb6366 100644 --- a/usvm-core/src/main/kotlin/org/usvm/Merging.kt +++ b/usvm-core/src/main/kotlin/org/usvm/Merging.kt @@ -1,7 +1,5 @@ package org.usvm -import org.usvm.memory.URegionHeap - interface UMerger { /** * @returns Merged entity or null if [left] and [right] are non-mergeable @@ -9,12 +7,7 @@ interface UMerger { fun merge(left: Entity, right: Entity): Entity? } -class URegionHeapMerger : UMerger> { - // Never merge for now - override fun merge(left: URegionHeap, right: URegionHeap) = null -} - -open class UStateMerger> : UMerger { +open class UStateMerger> : UMerger { // Never merge for now override fun merge(left: State, right: State) = null } diff --git a/usvm-core/src/main/kotlin/org/usvm/PathTrieNode.kt b/usvm-core/src/main/kotlin/org/usvm/PathTrieNode.kt index 3a568c3b5..948ae71ab 100644 --- a/usvm-core/src/main/kotlin/org/usvm/PathTrieNode.kt +++ b/usvm-core/src/main/kotlin/org/usvm/PathTrieNode.kt @@ -3,7 +3,7 @@ package org.usvm /** * Symbolic execution tree node. */ -sealed class PathsTrieNode, Statement> { +sealed class PathsTrieNode, Statement> { /** * Forked states' nodes. */ @@ -65,7 +65,7 @@ sealed class PathsTrieNode, Stateme } } -class PathsTrieNodeImpl, Statement> private constructor( +class PathsTrieNodeImpl, Statement> private constructor( override val depth: Int, override val states: MutableSet, // Note: order is important for tests @@ -101,7 +101,7 @@ class PathsTrieNodeImpl, Statement> override fun toString(): String = "Depth: $depth, statement: $statement" } -class RootNode, Statement> : PathsTrieNode() { +class RootNode, Statement> : PathsTrieNode() { override val children: MutableMap> = mutableMapOf() override val states: MutableSet = hashSetOf() diff --git a/usvm-core/src/main/kotlin/org/usvm/State.kt b/usvm-core/src/main/kotlin/org/usvm/State.kt index 4319fc806..0f175ab9f 100644 --- a/usvm-core/src/main/kotlin/org/usvm/State.kt +++ b/usvm-core/src/main/kotlin/org/usvm/State.kt @@ -2,7 +2,7 @@ package org.usvm import io.ksmt.expr.KInterpretedValue import org.usvm.constraints.UPathConstraints -import org.usvm.memory.UMemoryBase +import org.usvm.memory.UMemory import org.usvm.model.UModelBase import org.usvm.solver.USatResult import org.usvm.solver.UUnknownResult @@ -10,13 +10,13 @@ import org.usvm.solver.UUnsatResult typealias StateId = UInt -abstract class UState>( +abstract class UState>( // TODO: add interpreter-specific information ctx: UContext, open val callStack: UCallStack, open val pathConstraints: UPathConstraints, - open val memory: UMemoryBase, - open var models: List>, + open val memory: UMemory, + open var models: List>, open var pathLocation: PathsTrieNode, ) { /** @@ -54,7 +54,7 @@ abstract class UState + other as UState<*, *, *, *, *> return id == other.id } @@ -96,7 +96,7 @@ private const val OriginalState = false * forked state. * */ -private fun , Type, Field, Context : UContext> forkIfSat( +private fun , Type, Context : UContext> forkIfSat( state: T, newConstraintToOriginalState: UBoolExpr, newConstraintToForkedState: UBoolExpr, @@ -110,7 +110,7 @@ private fun , Type, Field, Context : U } else { newConstraintToOriginalState } - val solver = newConstraintToForkedState.uctx.solver() + val solver = newConstraintToForkedState.uctx.solver() val satResult = solver.checkWithSoftConstraints(constraintsToCheck) return when (satResult) { @@ -156,7 +156,7 @@ private fun , Type, Field, Context : U * 2. makes not more than one query to USolver; * 3. if both [condition] and ![condition] are satisfiable, then [ForkResult.positiveState] === [state]. */ -fun , Type, Field, Context : UContext> fork( +fun , Type, Context : UContext> fork( state: T, condition: UBoolExpr, ): ForkResult { @@ -217,7 +217,7 @@ fun , Type, Field, Context : UContext> * @return a list of states for each condition - `null` state * means [UUnknownResult] or [UUnsatResult] of checking condition. */ -fun , Type, Field, Context : UContext> forkMulti( +fun , Type, Context : UContext> forkMulti( state: T, conditions: Iterable, ): List { diff --git a/usvm-core/src/main/kotlin/org/usvm/StepScope.kt b/usvm-core/src/main/kotlin/org/usvm/StepScope.kt index 1a50fe42f..f018a8f6c 100644 --- a/usvm-core/src/main/kotlin/org/usvm/StepScope.kt +++ b/usvm-core/src/main/kotlin/org/usvm/StepScope.kt @@ -18,7 +18,7 @@ import org.usvm.StepScope.StepScopeState.DEAD * * @param originalState an initial state. */ -class StepScope, Type, Field, Context : UContext>( +class StepScope, Type, Context : UContext>( private val originalState: T, ) { private val forkedStates = mutableListOf() diff --git a/usvm-core/src/main/kotlin/org/usvm/UComponents.kt b/usvm-core/src/main/kotlin/org/usvm/UComponents.kt index a68a2e965..d4b83a615 100644 --- a/usvm-core/src/main/kotlin/org/usvm/UComponents.kt +++ b/usvm-core/src/main/kotlin/org/usvm/UComponents.kt @@ -7,7 +7,7 @@ import org.usvm.types.UTypeSystem * Provides core USVM components tuned for specific language. * Instatiated once per [UContext]. */ -interface UComponents { - fun mkSolver(ctx: Context): USolverBase +interface UComponents { + fun mkSolver(ctx: Context): USolverBase fun mkTypeSystem(ctx: UContext): UTypeSystem } \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/api/EngineApi.kt b/usvm-core/src/main/kotlin/org/usvm/api/EngineApi.kt new file mode 100644 index 000000000..60c5ea4fd --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/api/EngineApi.kt @@ -0,0 +1,14 @@ +package org.usvm.api + +import org.usvm.UBoolExpr +import org.usvm.UHeapRef +import org.usvm.UState + + +fun UState<*, *, *, *, *>.assume(expr: UBoolExpr) { + pathConstraints += expr +} + +fun UState<*, *, *, *, *>.objectTypeEquals(lhs: UHeapRef, rhs: UHeapRef): UBoolExpr { + TODO("Objects types equality check: $lhs, $rhs") +} diff --git a/usvm-core/src/main/kotlin/org/usvm/api/MemoryApi.kt b/usvm-core/src/main/kotlin/org/usvm/api/MemoryApi.kt new file mode 100644 index 000000000..2f1416e0f --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/api/MemoryApi.kt @@ -0,0 +1,93 @@ +package org.usvm.api + +import org.usvm.UBoolExpr +import org.usvm.UConcreteHeapRef +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.USort +import org.usvm.memory.UMemory +import org.usvm.memory.UReadOnlyMemory +import org.usvm.memory.UWritableMemory +import org.usvm.collection.array.UArrayIndexLValue +import org.usvm.collection.array.length.UArrayLengthLValue +import org.usvm.collection.field.UFieldLValue +import org.usvm.types.UTypeStream +import org.usvm.uctx +import org.usvm.collection.array.memcpy as memcpyInternal +import org.usvm.collection.array.memset as memsetInternal +import org.usvm.collection.array.allocateArray as allocateArrayInternal +import org.usvm.collection.array.allocateArrayInitialized as allocateArrayInitializedInternal + +fun UReadOnlyMemory.typeStreamOf(ref: UHeapRef): UTypeStream = + types.getTypeStream(ref) + +fun UMemory<*, *>.allocate() = ctx.mkConcreteHeapRef(addressCounter.freshAddress()) + +fun UReadOnlyMemory<*>.readField( + ref: UHeapRef, field: Field, sort: Sort +): UExpr = read(UFieldLValue(sort, ref, field)) + +fun UReadOnlyMemory<*>.readArrayIndex( + ref: UHeapRef, index: USizeExpr, arrayType: ArrayType, sort: Sort +): UExpr = read(UArrayIndexLValue(sort, ref, index, arrayType)) + +fun UReadOnlyMemory<*>.readArrayLength( + ref: UHeapRef, arrayType: ArrayType +): USizeExpr = read(UArrayLengthLValue(ref, arrayType)) + +fun UWritableMemory<*>.writeField( + ref: UHeapRef, field: Field, sort: Sort, value: UExpr, guard: UBoolExpr +) = write(UFieldLValue(sort, ref, field), value, guard) + +fun UWritableMemory<*>.writeArrayIndex( + ref: UHeapRef, index: USizeExpr, type: ArrayType, sort: Sort, value: UExpr, guard: UBoolExpr +) = write(UArrayIndexLValue(sort, ref, index, type), value, guard) + +fun UWritableMemory<*>.writeArrayLength( + ref: UHeapRef, size: USizeExpr, arrayType: ArrayType +) = write(UArrayLengthLValue(ref, arrayType), size, ref.uctx.trueExpr) + + +fun UWritableMemory<*>.memcpy( + srcRef: UHeapRef, + dstRef: UHeapRef, + type: ArrayType, + elementSort: Sort, + fromSrcIdx: USizeExpr, + fromDstIdx: USizeExpr, + toDstIdx: USizeExpr, + guard: UBoolExpr, +) { + memcpyInternal(srcRef, dstRef, type, elementSort, fromSrcIdx, fromDstIdx, toDstIdx, guard) +} + +fun UWritableMemory<*>.memcpy( + srcRef: UHeapRef, + dstRef: UHeapRef, + type: ArrayType, + elementSort: Sort, + fromSrc: USizeExpr, + fromDst: USizeExpr, + length: USizeExpr, +) { + val toDst = with(srcRef.uctx) { mkBvAddExpr(fromDst, mkBvSubExpr(length, mkSizeExpr(1))) } + memcpy(srcRef, dstRef, type, elementSort, fromSrc, fromDst, toDst, guard = srcRef.ctx.trueExpr) +} + +fun UWritableMemory.memset( + ref: UHeapRef, + type: ArrayType, + sort: Sort, + contents: Sequence> +) { + memsetInternal(ref, type, sort, contents) +} + +fun UWritableMemory.allocateArray( + type: ArrayType, count: USizeExpr +): UConcreteHeapRef = allocateArrayInternal(type, count) + +fun UWritableMemory.allocateArrayInitialized( + type: ArrayType, sort: Sort, contents: Sequence> +): UConcreteHeapRef = allocateArrayInitializedInternal(type, sort, contents) diff --git a/usvm-core/src/main/kotlin/org/usvm/api/MockApi.kt b/usvm-core/src/main/kotlin/org/usvm/api/MockApi.kt new file mode 100644 index 000000000..68f558277 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/api/MockApi.kt @@ -0,0 +1,39 @@ +package org.usvm.api + +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.USort +import org.usvm.UState +import org.usvm.uctx + +// TODO: special mock api for variables + +fun UState<*, Method, *, *, *>.makeSymbolicPrimitive( + sort: T +): UExpr { + check(sort != sort.uctx.addressSort) { "$sort is not primitive" } + return memory.mock { call(lastEnteredMethod, emptySequence(), sort) } +} + +fun UState.makeSymbolicRef(type: Type): UHeapRef { + val ref = memory.mock { call(lastEnteredMethod, emptySequence(), memory.ctx.addressSort) } + + memory.types.addSubtype(ref, type) + memory.types.addSupertype(ref, type) + + return ref +} + +fun UState.makeSymbolicArray(arrayType: Type, size: USizeExpr): UHeapRef { + val ref = memory.mock { call(lastEnteredMethod, emptySequence(), memory.ctx.addressSort) } + + memory.types.addSubtype(ref, arrayType) + memory.types.addSupertype(ref, arrayType) + + memory.writeArrayLength(ref, size, arrayType) + + return ref +} + +// TODO: add method call mocking diff --git a/usvm-core/src/main/kotlin/org/usvm/api/collection/ListCollectionApi.kt b/usvm-core/src/main/kotlin/org/usvm/api/collection/ListCollectionApi.kt new file mode 100644 index 000000000..164fd2dcb --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/api/collection/ListCollectionApi.kt @@ -0,0 +1,148 @@ +package org.usvm.api.collection + +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.USort +import org.usvm.UState +import org.usvm.api.memcpy +import org.usvm.api.readArrayIndex +import org.usvm.api.readArrayLength +import org.usvm.api.writeArrayIndex +import org.usvm.api.writeArrayLength +import org.usvm.memory.map + +object ListCollectionApi { + fun UState.mkSymbolicList( + listType: ListType + ): UHeapRef = with(memory.ctx) { + val ref = memory.alloc(listType) + memory.writeArrayLength(ref, mkSizeExpr(0), listType) + return ref + } + + fun UState.symbolicListSize( + listRef: UHeapRef, + listType: ListType + ): USizeExpr = with(memory.ctx) { + listRef.map( + concreteMapper = { concreteListRef -> + memory.readArrayLength(concreteListRef, listType) + }, + symbolicMapper = { symbolicListRef -> + memory.readArrayLength(symbolicListRef, listType).also { + pathConstraints += mkBvSignedGreaterOrEqualExpr(it, mkSizeExpr(0)) + } + } + ) + } + + fun UState.symbolicListGet( + listRef: UHeapRef, + index: USizeExpr, + listType: ListType, + sort: Sort + ): UExpr = with(memory.ctx) { + memory.readArrayIndex(listRef, index, listType, sort) + } + + fun UState.symbolicListAdd( + listRef: UHeapRef, + listType: ListType, + sort: Sort, + value: UExpr + ): Unit = with(memory.ctx) { + val size = symbolicListSize(listRef, listType) + + memory.writeArrayIndex(listRef, size, listType, sort, value, guard = trueExpr) + + val updatedSize = mkBvAddExpr(size, mkSizeExpr(1)) + memory.writeArrayLength(listRef, updatedSize, listType) + } + + fun UState.symbolicListSet( + listRef: UHeapRef, + listType: ListType, + sort: Sort, + index: USizeExpr, + value: UExpr + ) = with(memory.ctx) { + memory.writeArrayIndex(listRef, index, listType, sort, value, guard = trueExpr) + } + + fun UState.symbolicListInsert( + listRef: UHeapRef, + listType: ListType, + sort: Sort, + index: USizeExpr, + value: UExpr + ): Unit = with(memory.ctx) { + val currentSize = symbolicListSize(listRef, listType) + + val srcIndex = index + val indexAfterInsert = mkBvAddExpr(index, mkSizeExpr(1)) + val lastIndexAfterInsert = currentSize + + memory.memcpy( + srcRef = listRef, + dstRef = listRef, + type = listType, + elementSort = sort, + fromSrcIdx = srcIndex, + fromDstIdx = indexAfterInsert, + toDstIdx = lastIndexAfterInsert, + guard = trueExpr + ) + + memory.writeArrayIndex(listRef, index, listType, sort, value, guard = trueExpr) + + val updatedSize = mkBvAddExpr(currentSize, mkSizeExpr(1)) + memory.writeArrayLength(listRef, updatedSize, listType) + } + + fun UState.symbolicListRemove( + listRef: UHeapRef, + listType: ListType, + sort: Sort, + index: USizeExpr + ): Unit = with(memory.ctx) { + val currentSize = symbolicListSize(listRef, listType) + + val firstIndexAfterRemove = mkBvAddExpr(index, mkSizeExpr(1)) + val lastIndexAfterRemove = mkBvSubExpr(currentSize, mkSizeExpr(2)) + + memory.memcpy( + srcRef = listRef, + dstRef = listRef, + type = listType, + elementSort = sort, + fromSrcIdx = firstIndexAfterRemove, + fromDstIdx = index, + toDstIdx = lastIndexAfterRemove, + guard = trueExpr + ) + + val updatedSize = mkBvSubExpr(currentSize, mkSizeExpr(1)) + memory.writeArrayLength(listRef, updatedSize, listType) + } + + fun UState.symbolicListCopyRange( + srcRef: UHeapRef, + dstRef: UHeapRef, + listType: ListType, + sort: Sort, + srcFrom: USizeExpr, + dstFrom: USizeExpr, + length: USizeExpr + ): Unit = with(memory.ctx) { + memory.memcpy( + srcRef = srcRef, + dstRef = dstRef, + type = listType, + elementSort = sort, + fromSrc = srcFrom, + fromDst = dstFrom, + length = length + ) + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/api/collection/ObjectMapCollectionApi.kt b/usvm-core/src/main/kotlin/org/usvm/api/collection/ObjectMapCollectionApi.kt new file mode 100644 index 000000000..c8640208e --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/api/collection/ObjectMapCollectionApi.kt @@ -0,0 +1,122 @@ +package org.usvm.api.collection + +import io.ksmt.utils.mkFreshConst +import org.usvm.UBoolExpr +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.USort +import org.usvm.UState +import org.usvm.collection.map.length.UMapLengthLValue +import org.usvm.collection.map.ref.URefMapEntryLValue +import org.usvm.collection.map.ref.refMapMerge +import org.usvm.collection.set.USetEntryLValue +import org.usvm.collection.set.USetRegionId +import org.usvm.collection.set.setUnion +import org.usvm.memory.key.UHeapRefKeyInfo +import org.usvm.memory.map + +object ObjectMapCollectionApi { + fun UState.mkSymbolicObjectMap( + mapType: MapType + ): UHeapRef = with(memory.ctx) { + val ref = memory.alloc(mapType) + val length = UMapLengthLValue(ref, mapType) + memory.write(length, mkSizeExpr(0), trueExpr) + return ref + } + + // todo: input map size can be inconsistent with contains + fun UState.symbolicObjectMapSize( + mapRef: UHeapRef, + mapType: MapType + ): USizeExpr = with(memory.ctx) { + mapRef.map( + concreteMapper = { concreteMapRef -> + memory.read(UMapLengthLValue(concreteMapRef, mapType)) + }, + symbolicMapper = { symbolicMapRef -> + memory.read(UMapLengthLValue(symbolicMapRef, mapType)).also { + pathConstraints += mkBvSignedGreaterOrEqualExpr(it, mkSizeExpr(0)) + } + } + ) + } + + fun UState.symbolicObjectMapGet( + mapRef: UHeapRef, + key: UHeapRef, + mapType: MapType, + sort: Sort + ): UExpr = with(memory.ctx) { + memory.read(URefMapEntryLValue(sort, mapRef, key, mapType)) + } + + fun UState.symbolicObjectMapContains( + mapRef: UHeapRef, + key: UHeapRef, + mapType: MapType + ): UBoolExpr = with(memory.ctx) { + memory.read(USetEntryLValue(addressSort, mapRef, key, mapType, UHeapRefKeyInfo)) + } + + fun UState.symbolicObjectMapPut( + mapRef: UHeapRef, + key: UHeapRef, + value: UExpr, + mapType: MapType, + sort: Sort + ): Unit = with(memory.ctx) { + val mapContainsLValue = USetEntryLValue(addressSort, mapRef, key, mapType, UHeapRefKeyInfo) + + val keyIsInMap = memory.read(mapContainsLValue) + val keyIsNew = mkNot(keyIsInMap) + + memory.write(URefMapEntryLValue(sort, mapRef, key, mapType), value, guard = trueExpr) + memory.write(mapContainsLValue, rvalue = trueExpr, guard = trueExpr) + + val currentSize = symbolicObjectMapSize(mapRef, mapType) + val updatedSize = mkBvAddExpr(currentSize, mkSizeExpr(1)) + memory.write(UMapLengthLValue(mapRef, mapType), updatedSize, keyIsNew) + } + + fun UState.symbolicObjectMapRemove( + mapRef: UHeapRef, + key: UHeapRef, + mapType: MapType + ): Unit = with(memory.ctx) { + val mapContainsLValue = USetEntryLValue(addressSort, mapRef, key, mapType, UHeapRefKeyInfo) + + val keyIsInMap = memory.read(mapContainsLValue) + + // todo: skip values update? + memory.write(mapContainsLValue, rvalue = falseExpr, guard = trueExpr) + + val currentSize = symbolicObjectMapSize(mapRef, mapType) + val updatedSize = mkBvSubExpr(currentSize, mkSizeExpr(1)) + memory.write(UMapLengthLValue(mapRef, mapType), updatedSize, keyIsInMap) + } + + fun UState.symbolicObjectMapMergeInto( + dstRef: UHeapRef, + srcRef: UHeapRef, + mapType: MapType, + sort: Sort + ): Unit = with(memory.ctx) { + val containsSetId = USetRegionId(addressSort, mapType, UHeapRefKeyInfo) + + memory.refMapMerge(srcRef, dstRef, mapType, sort, containsSetId, guard = trueExpr) + + memory.setUnion(srcRef, dstRef, mapType, addressSort, UHeapRefKeyInfo, guard = trueExpr) + + // todo: precise map size approximation? + val mergedMapSize = sizeSort.mkFreshConst("mergedMapSize") + val srcMapSize = symbolicObjectMapSize(srcRef, mapType) + val dstMapSize = symbolicObjectMapSize(dstRef, mapType) + val sizeLowerBound = mkIte(mkBvSignedGreaterExpr(srcMapSize, dstMapSize), srcMapSize, dstMapSize) + val sizeUpperBound = mkBvAddExpr(srcMapSize, dstMapSize) + pathConstraints += mkBvSignedGreaterOrEqualExpr(mergedMapSize, sizeLowerBound) + pathConstraints += mkBvSignedGreaterOrEqualExpr(mergedMapSize, sizeUpperBound) + memory.write(UMapLengthLValue(dstRef, mapType), mergedMapSize, guard = trueExpr) + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/array/ArrayRegion.kt b/usvm-core/src/main/kotlin/org/usvm/collection/array/ArrayRegion.kt new file mode 100644 index 000000000..fc90c2cfa --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/array/ArrayRegion.kt @@ -0,0 +1,201 @@ +package org.usvm.collection.array + +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentHashMapOf +import org.usvm.UBoolExpr +import org.usvm.UConcreteHeapAddress +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.USort +import org.usvm.memory.key.USizeExprKeyInfo +import org.usvm.memory.ULValue +import org.usvm.memory.UMemoryRegion +import org.usvm.memory.UMemoryRegionId +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.foldHeapRef +import org.usvm.memory.map + +data class UArrayIndexLValue( + override val sort: Sort, + val ref: UHeapRef, + val index: USizeExpr, + val arrayType: ArrayType, +) : ULValue, Sort> { + + override val memoryRegionId: UMemoryRegionId, Sort> = + UArrayRegionId(arrayType, sort) + + override val key: UArrayIndexLValue + get() = this +} + +data class UArrayRegionId(val arrayType: ArrayType, override val sort: Sort) : + UMemoryRegionId, Sort> { + + override fun emptyRegion(): UMemoryRegion, Sort> = + UArrayMemoryRegion() +} + +typealias UAllocatedArray = USymbolicCollection, USizeExpr, Sort> +typealias UInputArray = USymbolicCollection, USymbolicArrayIndex, Sort> + +interface UArrayRegion : UMemoryRegion, Sort> { + fun memcpy( + srcRef: UHeapRef, + dstRef: UHeapRef, + type: ArrayType, + elementSort: Sort, + fromSrcIdx: USizeExpr, + fromDstIdx: USizeExpr, + toDstIdx: USizeExpr, + guard: UBoolExpr, + ): UArrayRegion + + fun initializeAllocatedArray( + address: UConcreteHeapAddress, + arrayType: ArrayType, + sort: Sort, + content: Map>, + guard: UBoolExpr + ): UArrayRegion +} + +internal class UArrayMemoryRegion( + private var allocatedArrays: PersistentMap> = persistentHashMapOf(), + private var inputArray: UInputArray? = null +) : UArrayRegion { + + private fun getAllocatedArray( + arrayType: ArrayType, + sort: Sort, + address: UConcreteHeapAddress + ): UAllocatedArray = allocatedArrays[address] + ?: UAllocatedArrayId(arrayType, sort, address).emptyRegion() + + private fun updateAllocatedArray(ref: UConcreteHeapAddress, updated: UAllocatedArray) = + UArrayMemoryRegion(allocatedArrays.put(ref, updated), inputArray) + + private fun getInputArray(arrayType: ArrayType, sort: Sort): UInputArray { + if (inputArray == null) + inputArray = UInputArrayId(arrayType, sort).emptyRegion() + return inputArray!! + } + + private fun updateInput(updated: UInputArray) = + UArrayMemoryRegion(allocatedArrays, updated) + + override fun read(key: UArrayIndexLValue): UExpr = + key.ref.map( + { concreteRef -> getAllocatedArray(key.arrayType, key.sort, concreteRef.address).read(key.index) }, + { symbolicRef -> getInputArray(key.arrayType, key.sort).read(symbolicRef to key.index) } + ) + + override fun write( + key: UArrayIndexLValue, + value: UExpr, + guard: UBoolExpr + ): UMemoryRegion, Sort> = + foldHeapRef( + key.ref, + initial = this, + initialGuard = guard, + blockOnConcrete = { region, (concreteRef, innerGuard) -> + val oldRegion = region.getAllocatedArray(key.arrayType, key.sort, concreteRef.address) + val newRegion = oldRegion.write(key.index, value, innerGuard) + region.updateAllocatedArray(concreteRef.address, newRegion) + }, + blockOnSymbolic = { region, (symbolicRef, innerGuard) -> + val oldRegion = region.getInputArray(key.arrayType, key.sort) + val newRegion = oldRegion.write(symbolicRef to key.index, value, innerGuard) + region.updateInput(newRegion) + } + ) + + override fun memcpy( + srcRef: UHeapRef, + dstRef: UHeapRef, + type: ArrayType, + elementSort: Sort, + fromSrcIdx: USizeExpr, + fromDstIdx: USizeExpr, + toDstIdx: USizeExpr, + guard: UBoolExpr, + ) = + foldHeapRef( + srcRef, + this, + guard, + blockOnConcrete = { outerRegion, (srcRef, guard) -> + foldHeapRef( + dstRef, + outerRegion, + guard, + blockOnConcrete = { region, (dstRef, deepGuard) -> + val srcCollection = region.getAllocatedArray(type, elementSort, srcRef.address) + val dstCollection = region.getAllocatedArray(type, elementSort, dstRef.address) + val adapter = USymbolicArrayAllocatedToAllocatedCopyAdapter( + fromSrcIdx, fromDstIdx, toDstIdx, USizeExprKeyInfo + ) + val newDstCollection = dstCollection.copyRange(srcCollection, adapter, deepGuard) + region.updateAllocatedArray(dstRef.address, newDstCollection) + }, + blockOnSymbolic = { region, (dstRef, deepGuard) -> + val srcCollection = region.getAllocatedArray(type, elementSort, srcRef.address) + val dstCollection = region.getInputArray(type, elementSort) + val adapter = USymbolicArrayAllocatedToInputCopyAdapter( + fromSrcIdx, + dstRef to fromDstIdx, + dstRef to toDstIdx, + USymbolicArrayIndexKeyInfo + ) + val newDstCollection = dstCollection.copyRange(srcCollection, adapter, deepGuard) + region.updateInput(newDstCollection) + }, + ) + }, + blockOnSymbolic = { outerRegion, (srcRef, guard) -> + foldHeapRef( + dstRef, + outerRegion, + guard, + blockOnConcrete = { region, (dstRef, deepGuard) -> + val srcCollection = region.getInputArray(type, elementSort) + val dstCollection = region.getAllocatedArray(type, elementSort, dstRef.address) + val adapter = USymbolicArrayInputToAllocatedCopyAdapter( + srcRef to fromSrcIdx, + fromDstIdx, + toDstIdx, + USizeExprKeyInfo + ) + val newDstCollection = dstCollection.copyRange(srcCollection, adapter, deepGuard) + region.updateAllocatedArray(dstRef.address, newDstCollection) + }, + blockOnSymbolic = { region, (dstRef, deepGuard) -> + val srcCollection = region.getInputArray(type, elementSort) + val dstCollection = region.getInputArray(type, elementSort) + val adapter = USymbolicArrayInputToInputCopyAdapter( + srcRef to fromSrcIdx, + dstRef to fromDstIdx, + dstRef to toDstIdx, + USymbolicArrayIndexKeyInfo + ) + val newDstCollection = dstCollection.copyRange(srcCollection, adapter, deepGuard) + region.updateInput(newDstCollection) + }, + ) + }, + ) + + override fun initializeAllocatedArray( + address: UConcreteHeapAddress, + arrayType: ArrayType, + sort: Sort, + content: Map>, + guard: UBoolExpr + ): UArrayMemoryRegion { + val arrayId = UAllocatedArrayId(arrayType, sort, address) + val newCollection = arrayId.initializedArray(content, guard) + return UArrayMemoryRegion(allocatedArrays.put(address, newCollection), inputArray) + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/array/ArrayRegionApi.kt b/usvm-core/src/main/kotlin/org/usvm/collection/array/ArrayRegionApi.kt new file mode 100644 index 000000000..218c9dc9b --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/array/ArrayRegionApi.kt @@ -0,0 +1,92 @@ +package org.usvm.collection.array + +import org.usvm.UBoolExpr +import org.usvm.UConcreteHeapRef +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.USort +import org.usvm.collection.array.length.UArrayLengthLValue +import org.usvm.memory.UWritableMemory +import org.usvm.uctx + +internal fun UWritableMemory<*>.memcpy( + srcRef: UHeapRef, + dstRef: UHeapRef, + type: ArrayType, + elementSort: Sort, + fromSrcIdx: USizeExpr, + fromDstIdx: USizeExpr, + toDstIdx: USizeExpr, + guard: UBoolExpr, +) { + val regionId = UArrayRegionId(type, elementSort) + val region = getRegion(regionId) + + check(region is UArrayRegion) { + "memcpy is not applicable to $region" + } + + val newRegion = region.memcpy(srcRef, dstRef, type, elementSort, fromSrcIdx, fromDstIdx, toDstIdx, guard) + setRegion(regionId, newRegion) +} + +internal fun UWritableMemory.allocateArrayInitialized( + type: ArrayType, + elementSort: Sort, + contents: Sequence> +): UConcreteHeapRef = with(elementSort.uctx) { + val arrayValues = hashMapOf>() + contents.forEachIndexed { idx, value -> arrayValues[mkSizeExpr(idx)] = value } + + val arrayLength = mkSizeExpr(arrayValues.size) + val address = allocateArray(type, arrayLength) + + val regionId = UArrayRegionId(type, elementSort) + val region = getRegion(regionId) + + check(region is UArrayRegion) { + "allocateArrayInitialized is not applicable to $region" + } + + val newRegion = region.initializeAllocatedArray(address.address, type, elementSort, arrayValues, guard = trueExpr) + + setRegion(regionId, newRegion) + + return address +} + +internal fun UWritableMemory.allocateArray( + type: ArrayType, + length: USizeExpr +): UConcreteHeapRef { + val address = alloc(type) + + val lengthRegionRef = UArrayLengthLValue(address, type) + write(lengthRegionRef, length, guard = length.uctx.trueExpr) + + return address +} + +internal fun UWritableMemory.memset( + ref: UHeapRef, + type: ArrayType, + sort: Sort, + contents: Sequence>, +) = with(sort.uctx) { + val tmpArrayRef = allocateArrayInitialized(type, sort, contents) + val contentLength = read(UArrayLengthLValue(tmpArrayRef, type)) + + memcpy( + srcRef = tmpArrayRef, + dstRef = ref, + type = type, + elementSort = sort, + fromSrcIdx = mkSizeExpr(0), + fromDstIdx = mkSizeExpr(0), + toDstIdx = contentLength, + guard = trueExpr + ) + + write(UArrayLengthLValue(ref, type), contentLength, guard = trueExpr) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/array/ArrayRegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/collection/array/ArrayRegionTranslator.kt new file mode 100644 index 000000000..2296a85aa --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/array/ArrayRegionTranslator.kt @@ -0,0 +1,199 @@ +package org.usvm.collection.array + +import io.ksmt.KContext +import io.ksmt.expr.KExpr +import io.ksmt.solver.KModel +import io.ksmt.sort.KArray2Sort +import io.ksmt.sort.KArraySort +import io.ksmt.utils.mkConst +import org.usvm.UAddressSort +import org.usvm.UConcreteHeapAddress +import org.usvm.UConcreteHeapRef +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.USizeSort +import org.usvm.USort +import org.usvm.memory.URangedUpdateNode +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.USymbolicCollectionId +import org.usvm.model.UMemory2DArray +import org.usvm.solver.U1DUpdatesTranslator +import org.usvm.solver.U2DUpdatesTranslator +import org.usvm.solver.UCollectionDecoder +import org.usvm.solver.UExprTranslator +import org.usvm.solver.URegionDecoder +import org.usvm.solver.URegionTranslator +import org.usvm.uctx +import java.util.IdentityHashMap + +class UArrayRegionDecoder( + private val regionId: UArrayRegionId, + private val exprTranslator: UExprTranslator<*> +) : URegionDecoder, Sort> { + + private val allocatedRegions = + mutableMapOf>() + + private var inputRegion: UInputArrayRegionTranslator? = null + + fun allocatedArrayRegionTranslator( + collectionId: UAllocatedArrayId + ): URegionTranslator, USizeExpr, Sort> = + allocatedRegions.getOrPut(collectionId.address) { + check(collectionId.arrayType == regionId.arrayType && collectionId.sort == regionId.sort) { + "Unexpected collection: $collectionId" + } + + UAllocatedArrayRegionTranslator(collectionId, exprTranslator) + } + + fun inputArrayRegionTranslator( + collectionId: UInputArrayId + ): URegionTranslator, USymbolicArrayIndex, Sort> { + if (inputRegion == null) { + check(collectionId.arrayType == regionId.arrayType && collectionId.sort == regionId.sort) { + "Unexpected collection: $collectionId" + } + inputRegion = UInputArrayRegionTranslator(collectionId, exprTranslator) + } + return inputRegion!! + } + + override fun decodeLazyRegion( + model: KModel, + mapping: Map + ) = UArrayLazyModelRegion(regionId, model, mapping, inputRegion) +} + +private class UAllocatedArrayRegionTranslator( + private val collectionId: UAllocatedArrayId, + exprTranslator: UExprTranslator<*> +) : URegionTranslator, USizeExpr, Sort> { + private val initialValue = with(collectionId.sort.uctx) { + val sort = mkArraySort(sizeSort, collectionId.sort) + val translatedDefaultValue = exprTranslator.translate(collectionId.defaultValue) + mkArrayConst(sort, translatedDefaultValue) + } + + private val visitorCache = IdentityHashMap>>() + private val updatesTranslator = UAllocatedArrayUpdatesTranslator(exprTranslator, initialValue) + + override fun translateReading( + region: USymbolicCollection, USizeExpr, Sort>, + key: USizeExpr + ): KExpr { + val translatedCollection = region.updates.accept(updatesTranslator, visitorCache) + return updatesTranslator.visitSelect(translatedCollection, key) + } +} + +private class UInputArrayRegionTranslator( + private val collectionId: UInputArrayId, + exprTranslator: UExprTranslator<*> +) : URegionTranslator, USymbolicArrayIndex, Sort>, + UCollectionDecoder { + private val initialValue = with(collectionId.sort.uctx) { + mkArraySort(addressSort, sizeSort, collectionId.sort).mkConst(collectionId.toString()) + } + + private val visitorCache = IdentityHashMap>>() + private val updatesTranslator = UInputArrayUpdatesTranslator(exprTranslator, initialValue) + + override fun translateReading( + region: USymbolicCollection, USymbolicArrayIndex, Sort>, + key: USymbolicArrayIndex + ): KExpr { + val translatedCollection = region.updates.accept(updatesTranslator, visitorCache) + return updatesTranslator.visitSelect(translatedCollection, key) + } + + override fun decodeCollection( + model: KModel, + mapping: Map + ): UReadOnlyMemoryRegion = + UMemory2DArray(initialValue, model, mapping) +} + +private class UAllocatedArrayUpdatesTranslator( + exprTranslator: UExprTranslator<*>, + initialValue: KExpr> +) : U1DUpdatesTranslator(exprTranslator, initialValue) { + override fun KContext.translateRangedUpdate( + previous: KExpr>, + update: URangedUpdateNode<*, *, USizeExpr, Sort> + ): KExpr> { + check(update.adapter is USymbolicArrayCopyAdapter<*, *>) { + "Unexpected array ranged operation: ${update.adapter}" + } + + @Suppress("UNCHECKED_CAST") + return translateArrayCopy( + previous, + update, + update.sourceCollection as USymbolicCollection, Any, Sort>, + update.adapter as USymbolicArrayCopyAdapter + ) + } + + private fun , SrcKey> KContext.translateArrayCopy( + previous: KExpr>, + update: URangedUpdateNode<*, *, USizeExpr, Sort>, + sourceCollection: USymbolicCollection, + adapter: USymbolicArrayCopyAdapter + ): KExpr> { + val key = mkFreshConst("k", previous.sort.domain) + + val keyMapper = sourceCollection.collectionId.keyMapper(exprTranslator) + val convertedKey = keyMapper(adapter.convert(key)) + + val isInside = update.includesSymbolically(key).translated // already includes guard + + val result = sourceCollection.collectionId.instantiate(sourceCollection, convertedKey).translated + + val ite = mkIte(isInside, result, previous.select(key)) + return mkArrayLambda(key.decl, ite) + } +} + +private class UInputArrayUpdatesTranslator( + exprTranslator: UExprTranslator<*>, + initialValue: KExpr> +) : U2DUpdatesTranslator(exprTranslator, initialValue) { + override fun KContext.translateRangedUpdate( + previous: KExpr>, + update: URangedUpdateNode<*, *, USymbolicArrayIndex, Sort> + ): KExpr> { + check(update.adapter is USymbolicArrayCopyAdapter<*, *>) { + "Unexpected array ranged operation: ${update.adapter}" + } + + @Suppress("UNCHECKED_CAST") + return translateArrayCopy( + previous, + update, + update.sourceCollection as USymbolicCollection, Any, Sort>, + update.adapter as USymbolicArrayCopyAdapter + ) + } + + private fun , SrcKey> KContext.translateArrayCopy( + previous: KExpr>, + update: URangedUpdateNode<*, *, USymbolicArrayIndex, Sort>, + sourceCollection: USymbolicCollection, + adapter: USymbolicArrayCopyAdapter + ): KExpr> { + val key1 = mkFreshConst("k1", previous.sort.domain0) + val key2 = mkFreshConst("k2", previous.sort.domain1) + + val keyMapper = sourceCollection.collectionId.keyMapper(exprTranslator) + val convertedKey = keyMapper(adapter.convert(key1 to key2)) + + val isInside = update.includesSymbolically(key1 to key2).translated // already includes guard + + val result = sourceCollection.collectionId.instantiate(sourceCollection, convertedKey).translated + + val ite = mkIte(isInside, result, previous.select(key1, key2)) + return mkArrayLambda(key1.decl, key2.decl, ite) + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/array/Expressions.kt b/usvm-core/src/main/kotlin/org/usvm/collection/array/Expressions.kt new file mode 100644 index 000000000..18fa46913 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/array/Expressions.kt @@ -0,0 +1,77 @@ +package org.usvm.collection.array + +import io.ksmt.cache.hash +import io.ksmt.cache.structurallyEqual +import io.ksmt.expr.KExpr +import io.ksmt.expr.printer.ExpressionPrinter +import io.ksmt.expr.transformer.KTransformerBase +import org.usvm.UCollectionReading +import org.usvm.UContext +import org.usvm.UHeapRef +import org.usvm.UNullRef +import org.usvm.USizeExpr +import org.usvm.USort +import org.usvm.UTransformer +import org.usvm.asTypedTransformer + +class UAllocatedArrayReading internal constructor( + ctx: UContext, + collection: UAllocatedArray, + val index: USizeExpr, +) : UCollectionReading, USizeExpr, Sort>(ctx, collection) { + override fun accept(transformer: KTransformerBase): KExpr { + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } + return transformer.asTypedTransformer().transform(this) + } + + override fun internEquals(other: Any): Boolean = + structurallyEqual( + other, + { collection }, + { index }, + ) + + override fun internHashCode(): Int = hash(collection, index) + + override fun print(printer: ExpressionPrinter) { + printer.append(collection.toString()) + printer.append("[") + printer.append(index) + printer.append("]") + } +} + +class UInputArrayReading internal constructor( + ctx: UContext, + collection: UInputArray, + val address: UHeapRef, + val index: USizeExpr +) : UCollectionReading, USymbolicArrayIndex, Sort>(ctx, collection) { + init { + require(address !is UNullRef) + } + + override fun accept(transformer: KTransformerBase): KExpr { + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } + return transformer.asTypedTransformer().transform(this) + } + + override fun internEquals(other: Any): Boolean = + structurallyEqual( + other, + { collection }, + { address }, + { index }, + ) + + override fun internHashCode(): Int = hash(collection, address, index) + + override fun print(printer: ExpressionPrinter) { + printer.append(collection.toString()) + printer.append("[") + printer.append(address) + printer.append(", ") + printer.append(index) + printer.append("]") + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/array/UArrayModelRegion.kt b/usvm-core/src/main/kotlin/org/usvm/collection/array/UArrayModelRegion.kt new file mode 100644 index 000000000..7dd591af7 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/array/UArrayModelRegion.kt @@ -0,0 +1,39 @@ +package org.usvm.collection.array + +import io.ksmt.solver.KModel +import org.usvm.UExpr +import org.usvm.USort +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.model.AddressesMapping +import org.usvm.model.modelEnsureConcreteInputRef +import org.usvm.sampleUValue +import org.usvm.solver.UCollectionDecoder + +abstract class UArrayModelRegion( + private val regionId: UArrayRegionId, +) : UReadOnlyMemoryRegion, Sort> { + val defaultValue by lazy { regionId.sort.sampleUValue() } + + abstract val inputArray: UReadOnlyMemoryRegion? + + override fun read(key: UArrayIndexLValue): UExpr { + val ref = modelEnsureConcreteInputRef(key.ref) ?: return defaultValue + return inputArray?.read(ref to key.index) ?: defaultValue + } +} + +class UArrayLazyModelRegion( + regionId: UArrayRegionId, + private val model: KModel, + private val addressesMapping: AddressesMapping, + private val inputArrayDecoder: UCollectionDecoder? +) : UArrayModelRegion(regionId) { + override val inputArray: UReadOnlyMemoryRegion? by lazy { + inputArrayDecoder?.decodeCollection(model, addressesMapping) + } +} + +class UArrayEagerModelRegion( + regionId: UArrayRegionId, + override val inputArray: UReadOnlyMemoryRegion? +) : UArrayModelRegion(regionId) diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/array/USymbolicArrayCopyAdapter.kt b/usvm-core/src/main/kotlin/org/usvm/collection/array/USymbolicArrayCopyAdapter.kt new file mode 100644 index 000000000..f07f810eb --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/array/USymbolicArrayCopyAdapter.kt @@ -0,0 +1,310 @@ +package org.usvm.collection.array + +import io.ksmt.utils.uncheckedCast +import org.usvm.UBoolExpr +import org.usvm.UComposer +import org.usvm.UContext +import org.usvm.UExpr +import org.usvm.USizeExpr +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.KeyMapper +import org.usvm.memory.USymbolicCollectionAdapter +import org.usvm.memory.USymbolicCollectionId +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.memory.UUpdateNode +import org.usvm.memory.UWritableMemory +import org.usvm.uctx +import org.usvm.util.Region + +/** + * Composable converter of symbolic collection keys. Helps to transparently copy content of various collections + * each into other without eager address conversion. + * For instance, when we copy array slice [i : i + len] to destination memory slice [j : j + len], + * we emulate it by memorizing the source memory updates as-is, but read the destination memory by + * 'redirecting' the index k to k + j - i of the source memory. + * This conversion is done by [convert]. + * Do not be confused: it converts [DstKey] to [SrcKey] (not vice-versa), as we use it when we + * read from destination buffer index to source memory. + */ +abstract class USymbolicArrayCopyAdapter( + val srcFrom: SrcKey, + val dstFrom: DstKey, + val dstTo: DstKey, + private val keyInfo: USymbolicCollectionKeyInfo +) : USymbolicCollectionAdapter { + + override val srcKey = srcFrom + + abstract val ctx: UContext + + override fun > region(): Reg = + keyInfo.keyRangeRegion(dstFrom, dstTo).uncheckedCast() + + /** + * Converts source memory key into destination memory key + */ + abstract override fun convert(key: DstKey): SrcKey + + protected fun convertIndex( + idx: USizeExpr, + dstFromIdx: USizeExpr, + srcFromIdx: USizeExpr + ): USizeExpr = with(ctx) { + mkBvAddExpr(mkBvSubExpr(idx, dstFromIdx), srcFromIdx) + } + + override fun includesConcretely(key: DstKey): Boolean = + keyInfo.cmpConcreteLe(dstFrom, key) && keyInfo.cmpConcreteLe(key, dstTo) + + override fun includesSymbolically(key: DstKey): UBoolExpr { + val leftIsLefter = keyInfo.cmpSymbolicLe(ctx, dstFrom, key) + val rightIsRighter = keyInfo.cmpSymbolicLe(ctx, key, dstTo) + val ctx = leftIsLefter.ctx + + return ctx.mkAnd(leftIsLefter, rightIsRighter) + } + + override fun isIncludedByUpdateConcretely( + update: UUpdateNode, + guard: UBoolExpr, + ): Boolean = + update.includesConcretely(dstFrom, guard) && update.includesConcretely(dstTo, guard) + + override fun mapDstKeys( + mappedSrcKey: MappedSrcKey, + srcCollectionId: USymbolicCollectionId<*, *, *>, + dstKeyMapper: KeyMapper, + composer: UComposer, + mappedKeyInfo: USymbolicCollectionKeyInfo + ): USymbolicCollectionAdapter? { + val mappedDstFrom = dstKeyMapper(dstFrom) ?: return null + val mappedDstTo = dstKeyMapper(dstTo) ?: return null + + if (srcKey === mappedSrcKey && dstFrom === mappedDstFrom && dstTo === mappedDstTo) { + @Suppress("UNCHECKED_CAST") + // In this case [MappedSrcKey] == [SrcKey] and [MappedDstKey] == [DstKey], + // but type system cannot type check that. + return this as USymbolicCollectionAdapter + } + + return mapKeyType( + mappedSrcKey, + concrete = { allocatedSrcKey -> + mapKeyType( + mappedDstFrom, + concrete = { allocatedDstFrom -> + USymbolicArrayAllocatedToAllocatedCopyAdapter( + allocatedSrcKey, + allocatedDstFrom, + ensureConcreteKey(mappedDstTo), + mappedKeyInfo.uncheckedCast() + ) + }, + symbolic = { symbolicDstFrom -> + USymbolicArrayAllocatedToInputCopyAdapter( + allocatedSrcKey, + symbolicDstFrom, + ensureSymbolicKey(mappedDstTo), + mappedKeyInfo.uncheckedCast() + ) + } + ) + }, + symbolic = { symbolicSrcKey -> + mapKeyType( + mappedDstFrom, + concrete = { allocatedDstFrom -> + USymbolicArrayInputToAllocatedCopyAdapter( + symbolicSrcKey, + allocatedDstFrom, + ensureConcreteKey(mappedDstTo), + mappedKeyInfo.uncheckedCast() + ) + }, + symbolic = { symbolicDstFrom -> + USymbolicArrayInputToInputCopyAdapter( + symbolicSrcKey, + symbolicDstFrom, + ensureSymbolicKey(mappedDstTo), + mappedKeyInfo.uncheckedCast() + ) + } + ) + } + ).uncheckedCast() + } + + abstract override fun applyTo( + memory: UWritableMemory, + srcCollectionId: USymbolicCollectionId, + dstCollectionId: USymbolicCollectionId, + guard: UBoolExpr + ) + + private fun keyToString(key: Key) = + mapKeyType( + key, + concrete = { "$it" }, + symbolic = { "${it.first}.${it.second}" } + ) + + override fun toString(collection: USymbolicCollection<*, SrcKey, *>): String { + return "[${keyToString(dstFrom)}..${keyToString(dstTo)}] <- $collection[${convert(dstFrom)}..${convert(dstTo)}]" + } + + companion object { + private inline fun mapKeyType( + key: Key, + concrete: (USizeExpr) -> T, + symbolic: (USymbolicArrayIndex) -> T + ): T = when (key) { + is UExpr<*> -> concrete(key.uncheckedCast()) + is Pair<*, *> -> symbolic(key.uncheckedCast()) + else -> error("Unexpected key: $key") + } + + private fun ensureSymbolicKey(key: Key): USymbolicArrayIndex = + mapKeyType(key, symbolic = { it }, concrete = { error("Key type mismatch: $key") }) + + private fun ensureConcreteKey(key: Key): USizeExpr = + mapKeyType(key, concrete = { it }, symbolic = { error("Key type mismatch: $key") }) + } +} + +class USymbolicArrayAllocatedToAllocatedCopyAdapter( + srcFrom: USizeExpr, dstFrom: USizeExpr, dstTo: USizeExpr, + keyInfo: USymbolicCollectionKeyInfo +) : USymbolicArrayCopyAdapter( + srcFrom, dstFrom, dstTo, keyInfo +) { + override val ctx: UContext + get() = srcFrom.uctx + + override fun convert(key: USizeExpr): USizeExpr = + convertIndex(key, dstFrom, srcFrom) + + override fun applyTo( + memory: UWritableMemory, + srcCollectionId: USymbolicCollectionId, + dstCollectionId: USymbolicCollectionId, + guard: UBoolExpr + ) = with(ctx) { + check(dstCollectionId is UAllocatedArrayId<*, *>) { "Unexpected collection: $dstCollectionId" } + check(srcCollectionId is UAllocatedArrayId<*, *>) { "Unexpected collection: $srcCollectionId" } + + memory.memcpy( + srcRef = mkConcreteHeapRef(srcCollectionId.address), + dstRef = mkConcreteHeapRef(dstCollectionId.address), + type = dstCollectionId.arrayType, + elementSort = dstCollectionId.sort, + fromSrcIdx = srcFrom, + fromDstIdx = dstFrom, + toDstIdx = dstTo, + guard = guard + ) + } +} + +class USymbolicArrayAllocatedToInputCopyAdapter( + srcFrom: USizeExpr, + dstFrom: USymbolicArrayIndex, dstTo: USymbolicArrayIndex, + keyInfo: USymbolicCollectionKeyInfo +) : USymbolicArrayCopyAdapter( + srcFrom, dstFrom, dstTo, keyInfo +) { + override val ctx: UContext + get() = srcFrom.uctx + + override fun convert(key: USymbolicArrayIndex): USizeExpr = + convertIndex(key.second, dstFrom.second, srcFrom) + + override fun applyTo( + memory: UWritableMemory, + srcCollectionId: USymbolicCollectionId, + dstCollectionId: USymbolicCollectionId, + guard: UBoolExpr + ) = with(ctx) { + check(dstCollectionId is USymbolicArrayId<*, *, *, *>) { "Unexpected collection: $dstCollectionId" } + check(srcCollectionId is UAllocatedArrayId<*, *>) { "Unexpected collection: $srcCollectionId" } + + memory.memcpy( + srcRef = mkConcreteHeapRef(srcCollectionId.address), + dstRef = dstFrom.first, + type = dstCollectionId.arrayType, + elementSort = dstCollectionId.sort, + fromSrcIdx = srcFrom, + fromDstIdx = dstFrom.second, + toDstIdx = dstTo.second, + guard = guard + ) + } +} + +class USymbolicArrayInputToAllocatedCopyAdapter( + srcFrom: USymbolicArrayIndex, dstFrom: USizeExpr, dstTo: USizeExpr, + keyInfo: USymbolicCollectionKeyInfo +) : USymbolicArrayCopyAdapter( + srcFrom, dstFrom, dstTo, keyInfo +) { + override val ctx: UContext + get() = dstFrom.uctx + + override fun convert(key: USizeExpr): USymbolicArrayIndex = + srcFrom.first to convertIndex(key, dstFrom, srcFrom.second) + + override fun applyTo( + memory: UWritableMemory, + srcCollectionId: USymbolicCollectionId, + dstCollectionId: USymbolicCollectionId, + guard: UBoolExpr + ) = with(ctx) { + check(dstCollectionId is UAllocatedArrayId<*, *>) { "Unexpected collection: $dstCollectionId" } + check(srcCollectionId is USymbolicArrayId<*, *, *, *>) { "Unexpected collection: $srcCollectionId" } + + memory.memcpy( + srcRef = srcFrom.first, + dstRef = mkConcreteHeapRef(dstCollectionId.address), + type = dstCollectionId.arrayType, + elementSort = dstCollectionId.sort, + fromSrcIdx = srcFrom.second, + fromDstIdx = dstFrom, + toDstIdx = dstTo, + guard = guard + ) + } +} + +class USymbolicArrayInputToInputCopyAdapter( + srcFrom: USymbolicArrayIndex, + dstFrom: USymbolicArrayIndex, dstTo: USymbolicArrayIndex, + keyInfo: USymbolicCollectionKeyInfo +) : USymbolicArrayCopyAdapter( + srcFrom, dstFrom, dstTo, keyInfo +) { + override val ctx: UContext + get() = srcFrom.second.uctx + + override fun convert(key: USymbolicArrayIndex): USymbolicArrayIndex = + srcFrom.first to convertIndex(key.second, dstFrom.second, srcFrom.second) + + override fun applyTo( + memory: UWritableMemory, + srcCollectionId: USymbolicCollectionId, + dstCollectionId: USymbolicCollectionId, + guard: UBoolExpr + ) = with(ctx) { + check(dstCollectionId is USymbolicArrayId<*, *, *, *>) { "Unexpected collection: $dstCollectionId" } + check(srcCollectionId is USymbolicArrayId<*, *, *, *>) { "Unexpected collection: $srcCollectionId" } + + memory.memcpy( + srcRef = srcFrom.first, + dstRef = dstFrom.first, + type = dstCollectionId.arrayType, + elementSort = dstCollectionId.sort, + fromSrcIdx = srcFrom.second, + fromDstIdx = dstFrom.second, + toDstIdx = dstTo.second, + guard = guard + ) + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/array/USymbolicArrayId.kt b/usvm-core/src/main/kotlin/org/usvm/collection/array/USymbolicArrayId.kt new file mode 100644 index 000000000..011c46943 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/array/USymbolicArrayId.kt @@ -0,0 +1,202 @@ +package org.usvm.collection.array + +import io.ksmt.cache.hash +import kotlinx.collections.immutable.toPersistentMap +import org.usvm.UBoolExpr +import org.usvm.UComposer +import org.usvm.UConcreteHeapAddress +import org.usvm.UConcreteHeapRef +import org.usvm.UContext +import org.usvm.UExpr +import org.usvm.USizeExpr +import org.usvm.USort +import org.usvm.UTransformer +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.UTreeUpdates +import org.usvm.memory.key.USizeExprKeyInfo +import org.usvm.memory.key.USizeRegion +import org.usvm.memory.DecomposedKey +import org.usvm.memory.KeyTransformer +import org.usvm.memory.ULValue +import org.usvm.memory.UPinpointUpdateNode +import org.usvm.memory.USymbolicCollectionId +import org.usvm.memory.USymbolicCollectionIdWithContextMemory +import org.usvm.memory.UUpdateNode +import org.usvm.memory.UWritableMemory +import org.usvm.sampleUValue +import org.usvm.util.RegionTree +import org.usvm.util.emptyRegionTree + + +interface USymbolicArrayId> : + USymbolicCollectionId { + val arrayType: ArrayType +} + +/** + * A collection id for a collection storing arrays allocated during execution. + * Each identifier contains information about its [arrayType] and [address]. + */ +class UAllocatedArrayId internal constructor( + override val arrayType: ArrayType, + override val sort: Sort, + val address: UConcreteHeapAddress, + val idDefaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null, +) : USymbolicCollectionIdWithContextMemory>(contextMemory), + USymbolicArrayId> { + + val defaultValue: UExpr by lazy { idDefaultValue ?: sort.sampleUValue() } + + override fun UContext.mkReading( + collection: USymbolicCollection, USizeExpr, Sort>, + key: USizeExpr + ): UExpr { + if (collection.updates.isEmpty()) { + return defaultValue + } + + return mkAllocatedArrayReading(collection, key) + } + + override fun UContext.mkLValue( + key: USizeExpr + ): ULValue<*, Sort> = UArrayIndexLValue(sort, mkConcreteHeapRef(address), key, arrayType) + + override fun keyMapper( + transformer: UTransformer, + ): KeyTransformer = { transformer.apply(it) } + + override fun map(composer: UComposer): UAllocatedArrayId { + val composedDefaultValue = composer.compose(defaultValue) + check(contextMemory == null) { "contextHeap is not null in composition" } + return UAllocatedArrayId(arrayType, sort, address, composedDefaultValue, composer.memory.toWritableMemory()) + } + + override fun keyInfo() = USizeExprKeyInfo + + override fun rebindKey(key: USizeExpr): DecomposedKey<*, Sort>? = + null + + override fun emptyRegion(): USymbolicCollection, USizeExpr, Sort> { + val updates = UTreeUpdates( + updates = emptyRegionTree(), + keyInfo() + ) + return USymbolicCollection(this, updates) + } + + fun initializedArray( + content: Map>, + guard: UBoolExpr + ): USymbolicCollection, USizeExpr, Sort> { + val emptyRegionTree = emptyRegionTree>() + + val entries = content.entries.associate { (key, value) -> + val region = USizeExprKeyInfo.keyToRegion(key) + val update = UPinpointUpdateNode(key, keyInfo(), value, guard) + region to (update to emptyRegionTree) + } + + val updates = UTreeUpdates( + updates = RegionTree(entries.toPersistentMap()), + keyInfo() + ) + + return USymbolicCollection(this, updates) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UAllocatedArrayId<*, *> + + if (address != other.address) return false + if (arrayType != other.arrayType) return false + if (sort != other.sort) return false + + return true + } + + override fun hashCode(): Int = address + + override fun toString(): String = "allocatedArray<$arrayType>($address)" +} + +/** + * A collection id for a collection storing arrays retrieved as a symbolic value, contains only its [arrayType]. + */ +class UInputArrayId internal constructor( + override val arrayType: ArrayType, + override val sort: Sort, + private val defaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null, +) : USymbolicCollectionIdWithContextMemory>(contextMemory), + USymbolicArrayId> { + + override fun UContext.mkReading( + collection: USymbolicCollection, USymbolicArrayIndex, Sort>, + key: USymbolicArrayIndex + ): UExpr = mkInputArrayReading(collection, key.first, key.second) + + override fun UContext.mkLValue( + key: USymbolicArrayIndex + ): ULValue<*, Sort> = UArrayIndexLValue(sort, key.first, key.second, arrayType) + + override fun keyMapper( + transformer: UTransformer, + ): KeyTransformer = { + 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 emptyRegion(): USymbolicCollection, USymbolicArrayIndex, Sort> { + val updates = UTreeUpdates( + updates = emptyRegionTree(), + keyInfo() + ) + return USymbolicCollection(this, updates) + } + + override fun map(composer: UComposer): UInputArrayId { + check(contextMemory == null) { "contextMemory is not null in composition" } + val composedDefault = composer.compose(sort.sampleUValue()) + return UInputArrayId(arrayType, sort, composedDefault, composer.memory.toWritableMemory()) + } + + override fun keyInfo(): USymbolicArrayIndexKeyInfo = + USymbolicArrayIndexKeyInfo + + override fun rebindKey(key: USymbolicArrayIndex): DecomposedKey<*, Sort>? = + when (val heapRef = key.first) { + is UConcreteHeapRef -> DecomposedKey( + UAllocatedArrayId( + arrayType, + sort, + heapRef.address, + defaultValue, + contextMemory + ), key.second + ) + + else -> null + } + + override fun toString(): String = "inputArray<$arrayType>" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UInputArrayId<*, *> + + if (arrayType != other.arrayType) return false + if (sort != other.sort) return false + + return true + } + + override fun hashCode(): Int = hash(arrayType, sort) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/array/USymbolicArrayIndexKeyInfo.kt b/usvm-core/src/main/kotlin/org/usvm/collection/array/USymbolicArrayIndexKeyInfo.kt new file mode 100644 index 000000000..e2f9aaa28 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/array/USymbolicArrayIndexKeyInfo.kt @@ -0,0 +1,65 @@ +package org.usvm.collection.array + +import org.usvm.UBoolExpr +import org.usvm.UContext +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.memory.key.UHeapRefKeyInfo +import org.usvm.memory.key.UHeapRefRegion +import org.usvm.memory.key.USizeExprKeyInfo +import org.usvm.memory.key.USizeRegion +import org.usvm.util.ProductRegion + +/** + * A composite key for symbolic arrays: every entry is determined by heap address of target buffer and its numeric index. + */ +typealias USymbolicArrayIndex = Pair +typealias USymbolicArrayIndexRegion = ProductRegion + +/** + * Provides information about keys of input arrays. + */ +object USymbolicArrayIndexKeyInfo: USymbolicCollectionKeyInfo { + override fun eqSymbolic(ctx: UContext, key1: USymbolicArrayIndex, key2: USymbolicArrayIndex): UBoolExpr = + with(ctx) { + UHeapRefKeyInfo.eqSymbolic(ctx, key1.first, key2.first) and + USizeExprKeyInfo.eqSymbolic(ctx, key1.second, key2.second) + } + + override fun eqConcrete(key1: USymbolicArrayIndex, key2: USymbolicArrayIndex): Boolean = + UHeapRefKeyInfo.eqConcrete(key1.first, key2.first) && USizeExprKeyInfo.eqConcrete(key1.second, key2.second) + + override fun cmpSymbolicLe(ctx: UContext, key1: USymbolicArrayIndex, key2: USymbolicArrayIndex): UBoolExpr = + with(ctx) { + UHeapRefKeyInfo.eqSymbolic(ctx, key1.first, key2.first) and + USizeExprKeyInfo.cmpSymbolicLe(ctx, key1.second, key2.second) + } + + override fun cmpConcreteLe(key1: USymbolicArrayIndex, key2: USymbolicArrayIndex): Boolean = + UHeapRefKeyInfo.eqConcrete(key1.first, key2.first) && USizeExprKeyInfo.cmpConcreteLe(key1.second, key2.second) + + override fun keyToRegion(key: USymbolicArrayIndex) = + ProductRegion( + UHeapRefKeyInfo.keyToRegion(key.first), + USizeExprKeyInfo.keyToRegion(key.second) + ) + + override fun keyRangeRegion(from: USymbolicArrayIndex, to: USymbolicArrayIndex): USymbolicArrayIndexRegion { + require(from.first == to.first) + return ProductRegion( + UHeapRefKeyInfo.keyToRegion(from.first), + USizeExprKeyInfo.keyRangeRegion(from.second, to.second) + ) + } + + override fun topRegion() = ProductRegion( + UHeapRefKeyInfo.topRegion(), + USizeExprKeyInfo.topRegion() + ) + + override fun bottomRegion() = ProductRegion( + UHeapRefKeyInfo.bottomRegion(), + USizeExprKeyInfo.bottomRegion() + ) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/array/length/ArrayLengthRegion.kt b/usvm-core/src/main/kotlin/org/usvm/collection/array/length/ArrayLengthRegion.kt new file mode 100644 index 000000000..aff541302 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/array/length/ArrayLengthRegion.kt @@ -0,0 +1,90 @@ +package org.usvm.collection.array.length + +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentMapOf +import org.usvm.UBoolExpr +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.USizeSort +import org.usvm.memory.ULValue +import org.usvm.memory.UMemoryRegion +import org.usvm.memory.UMemoryRegionId +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.foldHeapRef +import org.usvm.memory.guardedWrite +import org.usvm.memory.map +import org.usvm.uctx + +data class UArrayLengthLValue(val ref: UHeapRef, val arrayType: ArrayType) : + ULValue, USizeSort> { + + override val sort: USizeSort + get() = ref.uctx.sizeSort + + override val memoryRegionId: UMemoryRegionId, USizeSort> = + UArrayLengthsRegionId(sort, arrayType) + + override val key: UArrayLengthLValue + get() = this +} + +data class UArrayLengthsRegionId(override val sort: USizeSort, val arrayType: ArrayType) : + UMemoryRegionId, USizeSort> { + + override fun emptyRegion(): UMemoryRegion, USizeSort> = + UArrayLengthsMemoryRegion() +} + +typealias UAllocatedArrayLengths = PersistentMap, USizeExpr> +typealias UInputArrayLengths = USymbolicCollection, UHeapRef, USizeSort> + +interface UArrayLengthsRegion : UMemoryRegion, USizeSort> + +internal class UArrayLengthsMemoryRegion( + private val allocatedLengths: UAllocatedArrayLengths = persistentMapOf(), + private var inputLengths: UInputArrayLengths? = null +) : UArrayLengthsRegion { + + private fun readAllocated(id: UAllocatedArrayLengthId) = + allocatedLengths[id] ?: id.defaultValue + + private fun updateAllocated(updated: UAllocatedArrayLengths) = + UArrayLengthsMemoryRegion(updated, inputLengths) + + private fun getInputLength(ref: UArrayLengthLValue): UInputArrayLengths { + if (inputLengths == null) + inputLengths = UInputArrayLengthId(ref.arrayType, ref.sort).emptyRegion() + return inputLengths!! + } + + private fun updatedInput(updated: UInputArrayLengths) = + UArrayLengthsMemoryRegion(allocatedLengths, updated) + + override fun read(key: UArrayLengthLValue): USizeExpr = + key.ref.map( + { concreteRef -> readAllocated(UAllocatedArrayLengthId(key.arrayType, concreteRef.address, key.sort)) }, + { symbolicRef -> getInputLength(key).read(symbolicRef) } + ) + + override fun write( + key: UArrayLengthLValue, + value: USizeExpr, + guard: UBoolExpr + ) = foldHeapRef( + key.ref, + initial = this, + initialGuard = guard, + blockOnConcrete = { region, (concreteRef, innerGuard) -> + val id = UAllocatedArrayLengthId(key.arrayType, concreteRef.address, key.sort) + val newRegion = region.allocatedLengths.guardedWrite(id, value, innerGuard) { + id.defaultValue + } + region.updateAllocated(newRegion) + }, + blockOnSymbolic = { region, (symbolicRef, innerGuard) -> + val oldRegion = region.getInputLength(key) + val newRegion = oldRegion.write(symbolicRef, value, innerGuard) + region.updatedInput(newRegion) + } + ) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/array/length/ArrayLengthRegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/collection/array/length/ArrayLengthRegionTranslator.kt new file mode 100644 index 000000000..a0f86abd5 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/array/length/ArrayLengthRegionTranslator.kt @@ -0,0 +1,86 @@ +package org.usvm.collection.array.length + +import io.ksmt.KContext +import io.ksmt.expr.KExpr +import io.ksmt.solver.KModel +import io.ksmt.sort.KArraySort +import io.ksmt.utils.mkConst +import org.usvm.UAddressSort +import org.usvm.UConcreteHeapRef +import org.usvm.UHeapRef +import org.usvm.USizeSort +import org.usvm.memory.URangedUpdateNode +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.memory.USymbolicCollection +import org.usvm.model.UMemory1DArray +import org.usvm.solver.U1DUpdatesTranslator +import org.usvm.solver.UCollectionDecoder +import org.usvm.solver.UExprTranslator +import org.usvm.solver.URegionDecoder +import org.usvm.solver.URegionTranslator +import org.usvm.uctx +import java.util.IdentityHashMap + +class UArrayLengthRegionDecoder( + private val regionId: UArrayLengthsRegionId, + private val exprTranslator: UExprTranslator<*> +) : URegionDecoder, USizeSort> { + + private var inputArrayLengthTranslator: UInputArrayLengthRegionTranslator? = null + + fun inputArrayLengthRegionTranslator( + collectionId: UInputArrayLengthId + ): URegionTranslator, UHeapRef, USizeSort> { + if (inputArrayLengthTranslator == null) { + check(collectionId.arrayType == regionId.arrayType && collectionId.sort == regionId.sort) { + "Unexpected collection: $collectionId" + } + inputArrayLengthTranslator = UInputArrayLengthRegionTranslator(collectionId, exprTranslator) + } + return inputArrayLengthTranslator!! + } + + override fun decodeLazyRegion( + model: KModel, + mapping: Map + ) = UArrayLengthLazyModelRegion(regionId, model, mapping, inputArrayLengthTranslator) +} + +private class UInputArrayLengthRegionTranslator( + private val collectionId: UInputArrayLengthId, + exprTranslator: UExprTranslator<*> +) : URegionTranslator, UHeapRef, USizeSort>, + UCollectionDecoder { + private val initialValue = with(collectionId.sort.uctx) { + mkArraySort(addressSort, sizeSort).mkConst(collectionId.toString()) + } + + private val visitorCache = IdentityHashMap>>() + private val updatesTranslator = UInputArrayLengthUpdateTranslator(exprTranslator, initialValue) + + override fun translateReading( + region: USymbolicCollection, UHeapRef, USizeSort>, + key: UHeapRef + ): KExpr { + val translatedCollection = region.updates.accept(updatesTranslator, visitorCache) + return updatesTranslator.visitSelect(translatedCollection, key) + } + + override fun decodeCollection( + model: KModel, + mapping: Map + ): UReadOnlyMemoryRegion = + UMemory1DArray(initialValue, model, mapping) +} + +private class UInputArrayLengthUpdateTranslator( + exprTranslator: UExprTranslator<*>, + initialValue: KExpr> +) : U1DUpdatesTranslator(exprTranslator, initialValue) { + override fun KContext.translateRangedUpdate( + previous: KExpr>, + update: URangedUpdateNode<*, *, UHeapRef, USizeSort> + ): KExpr> { + error("Array length has no ranged updates") + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/array/length/Expressions.kt b/usvm-core/src/main/kotlin/org/usvm/collection/array/length/Expressions.kt new file mode 100644 index 000000000..5a5167428 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/array/length/Expressions.kt @@ -0,0 +1,40 @@ +package org.usvm.collection.array.length + +import io.ksmt.cache.hash +import io.ksmt.cache.structurallyEqual +import io.ksmt.expr.printer.ExpressionPrinter +import io.ksmt.expr.transformer.KTransformerBase +import org.usvm.UCollectionReading +import org.usvm.UContext +import org.usvm.UHeapRef +import org.usvm.UNullRef +import org.usvm.USizeExpr +import org.usvm.USizeSort +import org.usvm.UTransformer +import org.usvm.asTypedTransformer + +class UInputArrayLengthReading internal constructor( + ctx: UContext, + collection: UInputArrayLengths, + val address: UHeapRef, +) : UCollectionReading, UHeapRef, USizeSort>(ctx, collection) { + init { + require(address !is UNullRef) + } + + override fun accept(transformer: KTransformerBase): USizeExpr { + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } + return transformer.asTypedTransformer().transform(this) + } + + override fun internEquals(other: Any): Boolean = structurallyEqual(other, { collection }, { address }) + + override fun internHashCode(): Int = hash(collection, address) + + override fun print(printer: ExpressionPrinter) { + printer.append(collection.toString()) + printer.append("[") + printer.append(address) + printer.append("]") + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/array/length/UArrayLengthModelRegion.kt b/usvm-core/src/main/kotlin/org/usvm/collection/array/length/UArrayLengthModelRegion.kt new file mode 100644 index 000000000..c19bfaa4b --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/array/length/UArrayLengthModelRegion.kt @@ -0,0 +1,40 @@ +package org.usvm.collection.array.length + +import io.ksmt.solver.KModel +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USizeSort +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.model.AddressesMapping +import org.usvm.model.modelEnsureConcreteInputRef +import org.usvm.sampleUValue +import org.usvm.solver.UCollectionDecoder + +abstract class UArrayLengthModelRegion( + private val regionId: UArrayLengthsRegionId, +) : UReadOnlyMemoryRegion, USizeSort> { + val defaultValue by lazy { regionId.sort.sampleUValue() } + + abstract val inputArrayLength: UReadOnlyMemoryRegion? + + override fun read(key: UArrayLengthLValue): UExpr { + val ref = modelEnsureConcreteInputRef(key.ref) ?: return defaultValue + return inputArrayLength?.read(ref) ?: defaultValue + } +} + +class UArrayLengthLazyModelRegion( + regionId: UArrayLengthsRegionId, + private val model: KModel, + private val addressesMapping: AddressesMapping, + private val inputArrayLengthDecoder: UCollectionDecoder? +) : UArrayLengthModelRegion(regionId) { + override val inputArrayLength: UReadOnlyMemoryRegion? by lazy { + inputArrayLengthDecoder?.decodeCollection(model, addressesMapping) + } +} + +class UArrayLengthEagerModelRegion( + regionId: UArrayLengthsRegionId, + override val inputArrayLength: UReadOnlyMemoryRegion? +) : UArrayLengthModelRegion(regionId) diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/array/length/USymbolicArrayLengthId.kt b/usvm-core/src/main/kotlin/org/usvm/collection/array/length/USymbolicArrayLengthId.kt new file mode 100644 index 000000000..56ac0f15e --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/array/length/USymbolicArrayLengthId.kt @@ -0,0 +1,152 @@ +package org.usvm.collection.array.length + +import io.ksmt.cache.hash +import org.usvm.UComposer +import org.usvm.UConcreteHeapAddress +import org.usvm.UConcreteHeapRef +import org.usvm.UContext +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.USizeSort +import org.usvm.UTransformer +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.key.UHeapRefKeyInfo +import org.usvm.memory.key.USingleKeyInfo +import org.usvm.memory.DecomposedKey +import org.usvm.memory.KeyTransformer +import org.usvm.memory.UFlatUpdates +import org.usvm.memory.ULValue +import org.usvm.memory.USymbolicCollectionId +import org.usvm.memory.USymbolicCollectionIdWithContextMemory +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.memory.UWritableMemory +import org.usvm.sampleUValue + +interface USymbolicArrayLengthId> : + USymbolicCollectionId { + val arrayType: ArrayType +} + +/** + * An id for a collection storing the concretely allocated array length at heap address [address]. + */ +class UAllocatedArrayLengthId internal constructor( + override val arrayType: ArrayType, + val address: UConcreteHeapAddress, + override val sort: USizeSort, + val idDefaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null +) : USymbolicCollectionIdWithContextMemory>(contextMemory), + USymbolicArrayLengthId> { + + val defaultValue: USizeExpr by lazy { + idDefaultValue ?: sort.sampleUValue() + } + + override fun rebindKey(key: Unit): DecomposedKey<*, USizeSort>? = null + + override fun keyInfo(): USymbolicCollectionKeyInfo = USingleKeyInfo + + override fun toString(): String = "allocatedLength<$arrayType>($address)" + + override fun UContext.mkReading( + collection: USymbolicCollection, Unit, USizeSort>, + key: Unit + ): UExpr { + check(collection.updates.isEmpty()) { "Can't instantiate length reading from non-empty collection" } + return defaultValue + } + + override fun UContext.mkLValue( + key: Unit + ): ULValue<*, USizeSort> = UArrayLengthLValue(mkConcreteHeapRef(address), arrayType) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UAllocatedArrayLengthId<*> + + if (arrayType != other.arrayType) return false + if (address != other.address) return false + if (sort != other.sort) return false + + return true + } + + override fun hashCode(): Int = hash(address, arrayType, sort) + + override fun keyMapper(transformer: UTransformer): KeyTransformer = + error("This should not be called") + + override fun map(composer: UComposer): UAllocatedArrayLengthId = + error("This should not be called") + + override fun emptyRegion(): USymbolicCollection, Unit, USizeSort> = + error("This should not be called") +} + +/** + * A collection id for a collection storing array lengths for arrays of a specific [arrayType]. + */ +class UInputArrayLengthId internal constructor( + override val arrayType: ArrayType, + override val sort: USizeSort, + private val defaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null, +) : USymbolicCollectionIdWithContextMemory>(contextMemory), + USymbolicArrayLengthId> { + + override fun UContext.mkReading( + collection: USymbolicCollection, UHeapRef, USizeSort>, + key: UHeapRef + ): UExpr = mkInputArrayLengthReading(collection, key) + + override fun UContext.mkLValue( + key: UHeapRef + ): ULValue<*, USizeSort> = UArrayLengthLValue(key, arrayType) + + override fun keyMapper( + transformer: UTransformer, + ): KeyTransformer = { transformer.apply(it) } + + override fun map(composer: UComposer): UInputArrayLengthId { + check(contextMemory == null) { "contextMemory is not null in composition" } + val composedDefaultValue = composer.compose(sort.sampleUValue()) + return UInputArrayLengthId(arrayType, sort, composedDefaultValue, composer.memory.toWritableMemory()) + } + + override fun keyInfo() = UHeapRefKeyInfo + + override fun rebindKey(key: UHeapRef): DecomposedKey<*, USizeSort>? = when (key) { + is UConcreteHeapRef -> DecomposedKey( + UAllocatedArrayLengthId( + arrayType, + key.address, + sort, + defaultValue, + contextMemory + ), + Unit + ) + + else -> null + } + + override fun emptyRegion(): USymbolicCollection, UHeapRef, USizeSort> = + USymbolicCollection(this, UFlatUpdates(keyInfo())) + + override fun toString(): String = "length<$arrayType>()" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UInputArrayLengthId<*> + + return arrayType == other.arrayType + } + + override fun hashCode(): Int = arrayType.hashCode() +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/field/Expressions.kt b/usvm-core/src/main/kotlin/org/usvm/collection/field/Expressions.kt new file mode 100644 index 000000000..d5e1cfcae --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/field/Expressions.kt @@ -0,0 +1,40 @@ +package org.usvm.collection.field + +import io.ksmt.cache.hash +import io.ksmt.cache.structurallyEqual +import io.ksmt.expr.KExpr +import io.ksmt.expr.printer.ExpressionPrinter +import io.ksmt.expr.transformer.KTransformerBase +import org.usvm.UCollectionReading +import org.usvm.UContext +import org.usvm.UHeapRef +import org.usvm.UNullRef +import org.usvm.USort +import org.usvm.UTransformer + +class UInputFieldReading internal constructor( + ctx: UContext, + collection: UInputFields, + val address: UHeapRef, +) : UCollectionReading, UHeapRef, Sort>(ctx, collection) { + init { + require(address !is UNullRef) + } + + override fun accept(transformer: KTransformerBase): KExpr { + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } + // An unchecked cast here it to be able to choose the right overload from UExprTransformer + return transformer.transform(this) + } + + override fun internEquals(other: Any): Boolean = structurallyEqual(other, { collection }, { address }) + + override fun internHashCode(): Int = hash(collection, address) + + override fun print(printer: ExpressionPrinter) { + printer.append(collection.toString()) + printer.append("[") + printer.append(address) + printer.append("]") + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/field/FieldRegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/collection/field/FieldRegionTranslator.kt new file mode 100644 index 000000000..87b281364 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/field/FieldRegionTranslator.kt @@ -0,0 +1,85 @@ +package org.usvm.collection.field + +import io.ksmt.KContext +import io.ksmt.expr.KExpr +import io.ksmt.solver.KModel +import io.ksmt.sort.KArraySort +import io.ksmt.utils.mkConst +import org.usvm.UAddressSort +import org.usvm.UConcreteHeapRef +import org.usvm.UHeapRef +import org.usvm.USort +import org.usvm.memory.URangedUpdateNode +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.memory.USymbolicCollection +import org.usvm.model.UMemory1DArray +import org.usvm.solver.U1DUpdatesTranslator +import org.usvm.solver.UCollectionDecoder +import org.usvm.solver.UExprTranslator +import org.usvm.solver.URegionDecoder +import org.usvm.solver.URegionTranslator +import org.usvm.uctx +import java.util.IdentityHashMap + +class UFieldRegionDecoder( + private val regionId: UFieldsRegionId, + private val exprTranslator: UExprTranslator<*> +) : URegionDecoder, Sort> { + private var inputRegionTranslator: UInputFieldRegionTranslator? = null + + fun inputFieldRegionTranslator( + collectionId: UInputFieldId + ): URegionTranslator, UHeapRef, Sort> { + check(collectionId.field == regionId.field && collectionId.sort == regionId.sort) { + "Unexpected collection: $collectionId" + } + + if (inputRegionTranslator == null) { + inputRegionTranslator = UInputFieldRegionTranslator(collectionId, exprTranslator) + } + return inputRegionTranslator!! + } + + override fun decodeLazyRegion( + model: KModel, + mapping: Map + ) = UFieldsLazyModelRegion(regionId, model, mapping, inputRegionTranslator) +} + +private class UInputFieldRegionTranslator( + private val collectionId: UInputFieldId, + exprTranslator: UExprTranslator<*> +) : URegionTranslator, UHeapRef, Sort>, UCollectionDecoder { + private val initialValue = with(collectionId.sort.uctx) { + mkArraySort(addressSort, collectionId.sort).mkConst(collectionId.toString()) + } + + private val visitorCache = IdentityHashMap>>() + private val updatesTranslator = UInputFieldUpdateTranslator(exprTranslator, initialValue) + + override fun translateReading( + region: USymbolicCollection, UHeapRef, Sort>, + key: UHeapRef + ): KExpr { + val translatedCollection = region.updates.accept(updatesTranslator, visitorCache) + return updatesTranslator.visitSelect(translatedCollection, key) + } + + override fun decodeCollection( + model: KModel, + mapping: Map + ): UReadOnlyMemoryRegion = + UMemory1DArray(initialValue, model, mapping) +} + +private class UInputFieldUpdateTranslator( + exprTranslator: UExprTranslator<*>, + initialValue: KExpr> +) : U1DUpdatesTranslator(exprTranslator, initialValue) { + override fun KContext.translateRangedUpdate( + previous: KExpr>, + update: URangedUpdateNode<*, *, UHeapRef, Sort> + ): KExpr> { + error("Fields has no ranged updates") + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/field/FieldsRegion.kt b/usvm-core/src/main/kotlin/org/usvm/collection/field/FieldsRegion.kt new file mode 100644 index 000000000..96809d264 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/field/FieldsRegion.kt @@ -0,0 +1,89 @@ +package org.usvm.collection.field + +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentMapOf +import org.usvm.UBoolExpr +import org.usvm.UConcreteHeapAddress +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USort +import org.usvm.memory.ULValue +import org.usvm.memory.UMemoryRegion +import org.usvm.memory.UMemoryRegionId +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.foldHeapRef +import org.usvm.memory.guardedWrite +import org.usvm.memory.map + +data class UFieldLValue(override val sort: Sort, val ref: UHeapRef, val field: Field) : + ULValue, Sort> { + override val memoryRegionId: UMemoryRegionId, Sort> = + UFieldsRegionId(field, sort) + + override val key: UFieldLValue + get() = this +} + +data class UFieldsRegionId(val field: Field, override val sort: Sort) : + UMemoryRegionId, Sort> { + + override fun emptyRegion(): UMemoryRegion, Sort> = + UFieldsMemoryRegion() +} + +typealias UAllocatedFields = PersistentMap, UExpr> +typealias UInputFields = USymbolicCollection, UHeapRef, Sort> + +interface UFieldsRegion : UMemoryRegion, Sort> + +internal class UFieldsMemoryRegion( + private val allocatedFields: UAllocatedFields = persistentMapOf(), + private var inputFields: UInputFields? = null +) : UFieldsRegion { + + private fun readAllocated(address: UConcreteHeapAddress, field: Field, sort: Sort): UExpr { + val id = UAllocatedFieldId(field, address, sort) + return allocatedFields[id] ?: id.defaultValue + } + + private fun updateAllocated(updated: UAllocatedFields) = + UFieldsMemoryRegion(updated, inputFields) + + private fun getInputFields(ref: UFieldLValue): UInputFields { + if (inputFields == null) + inputFields = UInputFieldId(ref.field, ref.sort).emptyRegion() + return inputFields!! + } + + private fun updateInput(updated: UInputFields) = + UFieldsMemoryRegion(allocatedFields, updated) + + override fun read(key: UFieldLValue): UExpr = + key.ref.map( + { concreteRef -> readAllocated(concreteRef.address, key.field, key.sort) }, + { symbolicRef -> getInputFields(key).read(symbolicRef) } + ) + + override fun write( + key: UFieldLValue, + value: UExpr, + guard: UBoolExpr + ): UMemoryRegion, Sort> = + foldHeapRef( + key.ref, + initial = this, + initialGuard = guard, + blockOnConcrete = { region, (concreteRef, innerGuard) -> + val concreteKey = UAllocatedFieldId(key.field, concreteRef.address, key.sort) + val newRegion = region.allocatedFields.guardedWrite(concreteKey, value, innerGuard) { + concreteKey.defaultValue + } + region.updateAllocated(newRegion) + }, + blockOnSymbolic = { region, (symbolicRef, innerGuard) -> + val oldRegion = region.getInputFields(key) + val newRegion = oldRegion.write(symbolicRef, value, innerGuard) + region.updateInput(newRegion) + } + ) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/field/UFieldsModelRegion.kt b/usvm-core/src/main/kotlin/org/usvm/collection/field/UFieldsModelRegion.kt new file mode 100644 index 000000000..6c48f4e4f --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/field/UFieldsModelRegion.kt @@ -0,0 +1,40 @@ +package org.usvm.collection.field + +import io.ksmt.solver.KModel +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USort +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.model.AddressesMapping +import org.usvm.model.modelEnsureConcreteInputRef +import org.usvm.sampleUValue +import org.usvm.solver.UCollectionDecoder + +abstract class UFieldsModelRegion( + private val regionId: UFieldsRegionId, +) : UReadOnlyMemoryRegion, Sort> { + val defaultValue by lazy { regionId.sort.sampleUValue() } + + abstract val inputFields: UReadOnlyMemoryRegion? + + override fun read(key: UFieldLValue): UExpr { + val ref = modelEnsureConcreteInputRef(key.ref) ?: return defaultValue + return inputFields?.read(ref) ?: defaultValue + } +} + +class UFieldsLazyModelRegion( + regionId: UFieldsRegionId, + private val model: KModel, + private val addressesMapping: AddressesMapping, + private val inputFieldsDecoder: UCollectionDecoder? +) : UFieldsModelRegion(regionId) { + override val inputFields: UReadOnlyMemoryRegion? by lazy { + inputFieldsDecoder?.decodeCollection(model, addressesMapping) + } +} + +class UFieldsEagerModelRegion( + regionId: UFieldsRegionId, + override val inputFields: UReadOnlyMemoryRegion? +) : UFieldsModelRegion(regionId) diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/field/USymbolicFieldId.kt b/usvm-core/src/main/kotlin/org/usvm/collection/field/USymbolicFieldId.kt new file mode 100644 index 000000000..ab9624494 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/field/USymbolicFieldId.kt @@ -0,0 +1,152 @@ +package org.usvm.collection.field + +import io.ksmt.cache.hash +import org.usvm.UComposer +import org.usvm.UConcreteHeapAddress +import org.usvm.UConcreteHeapRef +import org.usvm.UContext +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USort +import org.usvm.UTransformer +import org.usvm.memory.DecomposedKey +import org.usvm.memory.KeyTransformer +import org.usvm.memory.ULValue +import org.usvm.memory.USymbolicCollectionId +import org.usvm.memory.USymbolicCollectionIdWithContextMemory +import org.usvm.memory.UWritableMemory +import org.usvm.memory.UFlatUpdates +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.key.UHeapRefKeyInfo +import org.usvm.memory.key.USingleKeyInfo +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.sampleUValue + +interface USymbolicFieldId> : + USymbolicCollectionId { + val field: Field +} + +/** + * An id for a collection storing the concretely allocated [field] at heap address [address]. + */ +class UAllocatedFieldId internal constructor( + override val field: Field, + val address: UConcreteHeapAddress, + override val sort: Sort, + val idDefaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null +) : USymbolicCollectionIdWithContextMemory>(contextMemory), + USymbolicFieldId> { + val defaultValue: UExpr by lazy { idDefaultValue ?: sort.sampleUValue() } + + override fun rebindKey(key: Unit): DecomposedKey<*, Sort>? = null + + override fun toString(): String = "allocatedField<$field>($address)" + + override fun keyInfo(): USymbolicCollectionKeyInfo = USingleKeyInfo + + override fun UContext.mkReading( + collection: USymbolicCollection, Unit, Sort>, + key: Unit + ): UExpr { + check(collection.updates.isEmpty()) { "Can't instantiate allocated field reading from non-empty collection" } + return defaultValue + } + + override fun UContext.mkLValue( + key: Unit + ): ULValue<*, Sort> = UFieldLValue(sort, mkConcreteHeapRef(address), field) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UAllocatedFieldId<*, *> + + if (field != other.field) return false + if (address != other.address) return false + if (sort != other.sort) return false + + return true + } + + override fun hashCode(): Int = hash(field, address, sort) + + override fun keyMapper(transformer: UTransformer): KeyTransformer = + error("This should not be called") + + override fun map(composer: UComposer): UAllocatedFieldId = + error("This should not be called") + + override fun emptyRegion(): USymbolicCollection, Unit, Sort> = + error("This should not be called") +} + +/** + * An id for a collection storing the specific [field]. + */ +class UInputFieldId internal constructor( + override val field: Field, + override val sort: Sort, + private val defaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null, +) : USymbolicCollectionIdWithContextMemory>(contextMemory), + USymbolicFieldId> { + + override fun UContext.mkReading( + collection: USymbolicCollection, UHeapRef, Sort>, + key: UHeapRef + ): UExpr = mkInputFieldReading(collection, key) + + override fun UContext.mkLValue( + key: UHeapRef + ): ULValue<*, Sort> = UFieldLValue(sort, key, field) + + override fun keyMapper( + transformer: UTransformer, + ): KeyTransformer = { transformer.apply(it) } + + override fun map(composer: UComposer): UInputFieldId { + check(contextMemory == null) { "contextMemory is not null in composition" } + val composedDefaultValue = composer.compose(sort.sampleUValue()) + return UInputFieldId(field, sort, composedDefaultValue, composer.memory.toWritableMemory()) + } + + override fun keyInfo() = UHeapRefKeyInfo + + override fun rebindKey(key: UHeapRef): DecomposedKey<*, Sort>? = + when (key) { + is UConcreteHeapRef -> DecomposedKey( + UAllocatedFieldId( + field, + key.address, + sort, + defaultValue, + contextMemory + ), + Unit + ) + + else -> null + } + + override fun emptyRegion(): USymbolicCollection, UHeapRef, Sort> = + USymbolicCollection(this, UFlatUpdates(keyInfo())) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UInputFieldId<*, *> + + if (field != other.field) return false + if (sort != other.sort) return false + + return true + } + + override fun hashCode(): Int = hash(field, sort) + + override fun toString(): String = "inputField<$field>()" +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/USymbolicMapKeyInfo.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/USymbolicMapKeyInfo.kt new file mode 100644 index 000000000..acbd2593e --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/USymbolicMapKeyInfo.kt @@ -0,0 +1,63 @@ +package org.usvm.collection.map + +import org.usvm.UBoolExpr +import org.usvm.UContext +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USort +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.memory.key.UHeapRefKeyInfo +import org.usvm.memory.key.UHeapRefRegion +import org.usvm.util.ProductRegion +import org.usvm.util.Region + +typealias USymbolicMapKey = Pair> +typealias USymbolicMapKeyRegion = ProductRegion + +/** + * Provides information about keys of symbolic maps. + */ +data class USymbolicMapKeyInfo>( + val keyInfo: USymbolicCollectionKeyInfo, KeyReg> +): USymbolicCollectionKeyInfo, USymbolicMapKeyRegion> { + override fun eqSymbolic(ctx: UContext, key1: USymbolicMapKey, key2: USymbolicMapKey): UBoolExpr = + with(ctx) { + UHeapRefKeyInfo.eqSymbolic(ctx, key1.first, key2.first) and + keyInfo.eqSymbolic(ctx, key1.second, key2.second) + } + + override fun eqConcrete(key1: USymbolicMapKey, key2: USymbolicMapKey): Boolean = + UHeapRefKeyInfo.eqConcrete(key1.first, key2.first) && keyInfo.eqConcrete(key1.second, key2.second) + + override fun cmpSymbolicLe(ctx: UContext, key1: USymbolicMapKey, key2: USymbolicMapKey): UBoolExpr = + with(ctx) { + UHeapRefKeyInfo.eqSymbolic(ctx, key1.first, key2.first) and + keyInfo.cmpSymbolicLe(ctx, key1.second, key2.second) + } + + override fun cmpConcreteLe(key1: USymbolicMapKey, key2: USymbolicMapKey): Boolean = + UHeapRefKeyInfo.eqConcrete(key1.first, key2.first) && keyInfo.cmpConcreteLe(key1.second, key2.second) + + override fun keyToRegion(key: USymbolicMapKey) = + ProductRegion( + UHeapRefKeyInfo.keyToRegion(key.first), + keyInfo.keyToRegion(key.second) + ) + + override fun keyRangeRegion( + from: USymbolicMapKey, + to: USymbolicMapKey + ): USymbolicMapKeyRegion { + require(from.first == to.first) + return ProductRegion( + UHeapRefKeyInfo.keyToRegion(from.first), + keyInfo.keyRangeRegion(from.second, to.second) + ) + } + + override fun topRegion() = + ProductRegion(UHeapRefKeyInfo.topRegion(), keyInfo.topRegion()) + + override fun bottomRegion() = + ProductRegion(UHeapRefKeyInfo.bottomRegion(), keyInfo.bottomRegion()) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/USymbolicMapMergeAdapter.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/USymbolicMapMergeAdapter.kt new file mode 100644 index 000000000..bef0814d3 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/USymbolicMapMergeAdapter.kt @@ -0,0 +1,164 @@ +package org.usvm.collection.map + +import io.ksmt.utils.uncheckedCast +import org.usvm.UBoolExpr +import org.usvm.UBoolSort +import org.usvm.UComposer +import org.usvm.UExpr +import org.usvm.USort +import org.usvm.memory.USymbolicCollection +import org.usvm.collection.map.primitive.USymbolicMapId +import org.usvm.collection.set.USymbolicSetId +import org.usvm.isTrue +import org.usvm.memory.KeyMapper +import org.usvm.memory.USymbolicCollectionAdapter +import org.usvm.memory.USymbolicCollectionId +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.memory.UUpdateNode +import org.usvm.memory.UWritableMemory +import org.usvm.util.Region + +abstract class USymbolicMapMergeAdapter( + val dstKey: DstKey, + override val srcKey: SrcKey, + val setOfKeys: USymbolicCollection, SrcKey, UBoolSort>, +) : USymbolicCollectionAdapter { + + abstract override fun convert(key: DstKey): SrcKey + + override fun includesConcretely(key: DstKey) = + includesSymbolically(key).isTrue + + override fun includesSymbolically(key: DstKey): UBoolExpr { + val srcKey = convert(key) + return setOfKeys.read(srcKey) // ??? + } + + override fun isIncludedByUpdateConcretely( + update: UUpdateNode, + guard: UBoolExpr, + ) = false + + override fun mapDstKeys( + mappedSrcKey: MappedSrcKey, + srcCollectionId: USymbolicCollectionId<*, *, *>, + dstKeyMapper: KeyMapper, + composer: UComposer, + mappedKeyInfo: USymbolicCollectionKeyInfo + ): USymbolicCollectionAdapter? { + check(srcCollectionId is USymbolicMapId<*, *, *, *, *>) { + "Unexpected collection: $srcCollectionId" + } + + val mappedDstKey = dstKeyMapper(dstKey) ?: return null + val mappedSetOfKeys = setOfKeys.mapTo(composer, srcCollectionId.keysSetId) + + if (mappedSrcKey == srcKey && mappedDstKey == dstKey && mappedSetOfKeys == setOfKeys) { + @Suppress("UNCHECKED_CAST") + return this as USymbolicCollectionAdapter + } + + return mapKeyType( + key = mappedSrcKey, + concrete = { concreteSrc -> + mapKeyType( + key = mappedDstKey, + concrete = { concreteDst -> + USymbolicMapAllocatedToAllocatedMergeAdapter( + concreteDst, concreteSrc, mappedSetOfKeys.uncheckedCast() + ) + }, + symbolic = { symbolicDst -> + USymbolicMapAllocatedToInputMergeAdapter( + symbolicDst, concreteSrc, mappedSetOfKeys.uncheckedCast() + ) + } + ) + }, + symbolic = { symbolicSrc -> + mapKeyType( + key = mappedDstKey, + concrete = { concreteDst -> + USymbolicMapInputToAllocatedMergeAdapter( + concreteDst, symbolicSrc, mappedSetOfKeys.uncheckedCast() + ) + }, + symbolic = { symbolicDst -> + USymbolicMapInputToInputMergeAdapter( + symbolicDst, symbolicSrc, mappedSetOfKeys.uncheckedCast() + ) + } + ) + } + ).uncheckedCast() + } + + override fun toString(collection: USymbolicCollection<*, SrcKey, *>): String = + "(merge $collection)" + + override fun applyTo( + memory: UWritableMemory, + srcCollectionId: USymbolicCollectionId, + dstCollectionId: USymbolicCollectionId, + guard: UBoolExpr + ) { + TODO("Not yet implemented") + } + + override fun > region(): Reg = + convertRegion(setOfKeys.collectionId.region(setOfKeys.updates)) + + private fun > convertRegion(srcReg: Reg): Reg = + srcReg // TODO: implement valid region conversion logic + + companion object { + private inline fun mapKeyType( + key: Key, + concrete: (UExpr) -> T, + symbolic: (USymbolicMapKey) -> T + ): T = when (key) { + is UExpr<*> -> concrete(key.uncheckedCast()) + is Pair<*, *> -> symbolic(key.uncheckedCast()) + else -> error("Unexpected key: $key") + } + } +} + +class USymbolicMapAllocatedToAllocatedMergeAdapter( + dstKey: UExpr, srcKey: UExpr, + setOfKeys: USymbolicCollection, *, *>, UExpr, UBoolSort> +) : USymbolicMapMergeAdapter, UExpr>( + dstKey, srcKey, setOfKeys +) { + override fun convert(key: UExpr): UExpr = key +} + +class USymbolicMapAllocatedToInputMergeAdapter( + dstKey: USymbolicMapKey, + srcKey: UExpr, + setOfKeys: USymbolicCollection, *, *>, UExpr, UBoolSort> +) : USymbolicMapMergeAdapter, USymbolicMapKey>( + dstKey, srcKey, setOfKeys +) { + override fun convert(key: USymbolicMapKey): UExpr = key.second +} + +class USymbolicMapInputToAllocatedMergeAdapter( + dstKey: UExpr, + srcKey: USymbolicMapKey, + setOfKeys: USymbolicCollection, *, *>, USymbolicMapKey, UBoolSort> +) : USymbolicMapMergeAdapter, UExpr>( + dstKey, srcKey, setOfKeys +) { + override fun convert(key: UExpr): USymbolicMapKey = srcKey.first to key +} + +class USymbolicMapInputToInputMergeAdapter( + dstKey: USymbolicMapKey, + srcKey: USymbolicMapKey, + setOfKeys: USymbolicCollection, *, *>, USymbolicMapKey, UBoolSort> +) : USymbolicMapMergeAdapter, USymbolicMapKey>( + dstKey, srcKey, setOfKeys +) { + override fun convert(key: USymbolicMapKey): USymbolicMapKey = srcKey.first to key.second +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/length/Expressions.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/length/Expressions.kt new file mode 100644 index 000000000..75c3e9490 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/length/Expressions.kt @@ -0,0 +1,40 @@ +package org.usvm.collection.map.length + +import io.ksmt.cache.hash +import io.ksmt.cache.structurallyEqual +import io.ksmt.expr.printer.ExpressionPrinter +import io.ksmt.expr.transformer.KTransformerBase +import org.usvm.UCollectionReading +import org.usvm.UContext +import org.usvm.UHeapRef +import org.usvm.UNullRef +import org.usvm.USizeExpr +import org.usvm.USizeSort +import org.usvm.UTransformer +import org.usvm.asTypedTransformer + +class UInputMapLengthReading internal constructor( + ctx: UContext, + collection: UInputMapLengthCollection, + val address: UHeapRef, +) : UCollectionReading, UHeapRef, USizeSort>(ctx, collection) { + init { + require(address !is UNullRef) + } + + override fun accept(transformer: KTransformerBase): USizeExpr { + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } + return transformer.asTypedTransformer().transform(this) + } + + override fun internEquals(other: Any): Boolean = structurallyEqual(other, { collection }, { address }) + + override fun internHashCode(): Int = hash(collection, address) + + override fun print(printer: ExpressionPrinter) { + printer.append(collection.toString()) + printer.append("[") + printer.append(address) + printer.append("]") + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/length/UMapLengthModelRegion.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/length/UMapLengthModelRegion.kt new file mode 100644 index 000000000..00fb58490 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/length/UMapLengthModelRegion.kt @@ -0,0 +1,40 @@ +package org.usvm.collection.map.length + +import io.ksmt.solver.KModel +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USizeSort +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.model.AddressesMapping +import org.usvm.model.modelEnsureConcreteInputRef +import org.usvm.sampleUValue +import org.usvm.solver.UCollectionDecoder + +abstract class UMapLengthModelRegion( + private val regionId: UMapLengthRegionId, +) : UReadOnlyMemoryRegion, USizeSort> { + val defaultValue by lazy { regionId.sort.sampleUValue() } + + abstract val inputMapLength: UReadOnlyMemoryRegion? + + override fun read(key: UMapLengthLValue): UExpr { + val ref = modelEnsureConcreteInputRef(key.ref) ?: return defaultValue + return inputMapLength?.read(ref) ?: defaultValue + } +} + +class UMapLengthLazyModelRegion( + regionId: UMapLengthRegionId, + private val model: KModel, + private val addressesMapping: AddressesMapping, + private val inputLengthDecoder: UCollectionDecoder? +) : UMapLengthModelRegion(regionId) { + override val inputMapLength: UReadOnlyMemoryRegion? by lazy { + inputLengthDecoder?.decodeCollection(model, addressesMapping) + } +} + +class UMapLengthEagerModelRegion( + regionId: UMapLengthRegionId, + override val inputMapLength: UReadOnlyMemoryRegion? +) : UMapLengthModelRegion(regionId) diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/length/UMapLengthRegion.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/length/UMapLengthRegion.kt new file mode 100644 index 000000000..cbb2c3bbb --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/length/UMapLengthRegion.kt @@ -0,0 +1,93 @@ +package org.usvm.collection.map.length + +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentMapOf +import org.usvm.UBoolExpr +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.USizeSort +import org.usvm.memory.ULValue +import org.usvm.memory.UMemoryRegion +import org.usvm.memory.UMemoryRegionId +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.guardedWrite +import org.usvm.memory.foldHeapRef +import org.usvm.memory.map +import org.usvm.uctx + +typealias UInputMapLengthCollection = USymbolicCollection, UHeapRef, USizeSort> + +data class UMapLengthLValue(val ref: UHeapRef, val mapType: MapType) : + ULValue, USizeSort> { + + override val sort: USizeSort + get() = ref.uctx.sizeSort + + override val memoryRegionId: UMemoryRegionId, USizeSort> = + UMapLengthRegionId(sort, mapType) + + override val key: UMapLengthLValue + get() = this +} + +data class UMapLengthRegionId(override val sort: USizeSort, val mapType: MapType) : + UMemoryRegionId, USizeSort> { + + override fun emptyRegion(): UMemoryRegion, USizeSort> = + UMapLengthMemoryRegion() +} + +typealias UAllocatedMapLength = PersistentMap, USizeExpr> +typealias UInputMapLength = USymbolicCollection, UHeapRef, USizeSort> + +interface UMapLengthRegion : UMemoryRegion, USizeSort> + +internal class UMapLengthMemoryRegion( + private val allocatedLengths: UAllocatedMapLength = persistentMapOf(), + private var inputLengths: UInputMapLength? = null +) : UMapLengthRegion { + + private fun readAllocated(id: UAllocatedMapLengthId) = + allocatedLengths[id] ?: id.defaultValue + + private fun updateAllocated(updated: UAllocatedMapLength) = + UMapLengthMemoryRegion(updated, inputLengths) + + private fun getInputLength(ref: UMapLengthLValue): UInputMapLength { + if (inputLengths == null) + inputLengths = UInputMapLengthId(ref.mapType, ref.sort).emptyRegion() + return inputLengths!! + } + + private fun updateInput(updated: UInputMapLength) = + UMapLengthMemoryRegion(allocatedLengths, updated) + + override fun read(key: UMapLengthLValue): USizeExpr = + key.ref.map( + { concreteRef -> readAllocated(UAllocatedMapLengthId(key.mapType, concreteRef.address, key.sort)) }, + { symbolicRef -> getInputLength(key).read(symbolicRef) } + ) + + override fun write( + key: UMapLengthLValue, + value: UExpr, + guard: UBoolExpr + ) = foldHeapRef( + ref = key.ref, + initial = this, + initialGuard = guard, + blockOnConcrete = { region, (concreteRef, innerGuard) -> + val id = UAllocatedMapLengthId(key.mapType, concreteRef.address, key.sort) + val newRegion = region.allocatedLengths.guardedWrite(id, value, innerGuard) { + id.defaultValue + } + region.updateAllocated(newRegion) + }, + blockOnSymbolic = { region, (symbolicRef, innerGuard) -> + val oldRegion = region.getInputLength(key) + val newRegion = oldRegion.write(symbolicRef, value, innerGuard) + region.updateInput(newRegion) + } + ) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/length/UMapLengthRegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/length/UMapLengthRegionTranslator.kt new file mode 100644 index 000000000..dc0929165 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/length/UMapLengthRegionTranslator.kt @@ -0,0 +1,86 @@ +package org.usvm.collection.map.length + +import io.ksmt.KContext +import io.ksmt.expr.KExpr +import io.ksmt.solver.KModel +import io.ksmt.sort.KArraySort +import io.ksmt.utils.mkConst +import org.usvm.UAddressSort +import org.usvm.UConcreteHeapRef +import org.usvm.UHeapRef +import org.usvm.USizeSort +import org.usvm.memory.URangedUpdateNode +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.memory.USymbolicCollection +import org.usvm.model.UMemory1DArray +import org.usvm.solver.U1DUpdatesTranslator +import org.usvm.solver.UCollectionDecoder +import org.usvm.solver.UExprTranslator +import org.usvm.solver.URegionDecoder +import org.usvm.solver.URegionTranslator +import org.usvm.uctx +import java.util.IdentityHashMap + +class UMapLengthRegionDecoder( + private val regionId: UMapLengthRegionId, + private val exprTranslator: UExprTranslator<*> +) : URegionDecoder, USizeSort> { + + private var inputTranslator: UInputMapLengthRegionTranslator? = null + + fun inputMapLengthRegionTranslator( + collectionId: UInputMapLengthId + ): URegionTranslator, UHeapRef, USizeSort> { + if (inputTranslator == null) { + check(collectionId.mapType == regionId.mapType && collectionId.sort == regionId.sort) { + "Unexpected collection: $collectionId" + } + inputTranslator = UInputMapLengthRegionTranslator(collectionId, exprTranslator) + } + return inputTranslator!! + } + + override fun decodeLazyRegion( + model: KModel, + mapping: Map + ) = UMapLengthLazyModelRegion(regionId, model, mapping, inputTranslator) +} + +private class UInputMapLengthRegionTranslator( + private val collectionId: UInputMapLengthId, + exprTranslator: UExprTranslator<*> +) : URegionTranslator, UHeapRef, USizeSort>, + UCollectionDecoder { + private val initialValue = with(collectionId.sort.uctx) { + mkArraySort(addressSort, sizeSort).mkConst(collectionId.toString()) + } + + private val visitorCache = IdentityHashMap>>() + private val updatesTranslator = UInputMapLengthUpdateTranslator(exprTranslator, initialValue) + + override fun translateReading( + region: USymbolicCollection, UHeapRef, USizeSort>, + key: UHeapRef + ): KExpr { + val translatedCollection = region.updates.accept(updatesTranslator, visitorCache) + return updatesTranslator.visitSelect(translatedCollection, key) + } + + override fun decodeCollection( + model: KModel, + mapping: Map + ): UReadOnlyMemoryRegion = + UMemory1DArray(initialValue, model, mapping) +} + +private class UInputMapLengthUpdateTranslator( + exprTranslator: UExprTranslator<*>, + initialValue: KExpr> +) : U1DUpdatesTranslator(exprTranslator, initialValue) { + override fun KContext.translateRangedUpdate( + previous: KExpr>, + update: URangedUpdateNode<*, *, UHeapRef, USizeSort> + ): KExpr> { + error("Map length has no ranged updates") + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/length/USymbolicMapLengthId.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/length/USymbolicMapLengthId.kt new file mode 100644 index 000000000..2d13ae3db --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/length/USymbolicMapLengthId.kt @@ -0,0 +1,142 @@ +package org.usvm.collection.map.length + +import io.ksmt.cache.hash +import org.usvm.UComposer +import org.usvm.UConcreteHeapAddress +import org.usvm.UConcreteHeapRef +import org.usvm.UContext +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.USizeSort +import org.usvm.UTransformer +import org.usvm.memory.DecomposedKey +import org.usvm.memory.KeyTransformer +import org.usvm.memory.ULValue +import org.usvm.memory.USymbolicCollectionId +import org.usvm.memory.USymbolicCollectionIdWithContextMemory +import org.usvm.memory.UWritableMemory +import org.usvm.memory.UFlatUpdates +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.key.UHeapRefKeyInfo +import org.usvm.memory.key.USingleKeyInfo +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.sampleUValue + +interface USymbolicMapLengthId> : + USymbolicCollectionId { + val mapType: MapType +} + +class UAllocatedMapLengthId internal constructor( + override val mapType: MapType, + val address: UConcreteHeapAddress, + override val sort: USizeSort, + val idDefaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null, +) : USymbolicCollectionIdWithContextMemory>(contextMemory), + USymbolicMapLengthId> { + + val defaultValue: USizeExpr by lazy { idDefaultValue ?: sort.sampleUValue() } + + override fun rebindKey(key: Unit): DecomposedKey<*, USizeSort>? = null + + override fun keyInfo(): USymbolicCollectionKeyInfo = USingleKeyInfo + + override fun toString(): String = "allocatedLength<$mapType>($address)" + + override fun UContext.mkReading( + collection: USymbolicCollection, Unit, USizeSort>, + key: Unit + ): UExpr { + check(collection.updates.isEmpty()) { "Can't instantiate length reading from non-empty collection" } + return defaultValue + } + + override fun UContext.mkLValue( + key: Unit + ): ULValue<*, USizeSort> = UMapLengthLValue(mkConcreteHeapRef(address), mapType) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UAllocatedMapLengthId<*> + + if (mapType != other.mapType) return false + if (address != other.address) return false + + return true + } + + override fun hashCode(): Int = hash(mapType, address) + + override fun emptyRegion(): USymbolicCollection, Unit, USizeSort> = + error("This should not be called") + + override fun keyMapper(transformer: UTransformer): KeyTransformer = + error("This should not be called") + + override fun map(composer: UComposer): UAllocatedMapLengthId = + error("This should not be called") +} + +class UInputMapLengthId internal constructor( + override val mapType: MapType, + override val sort: USizeSort, + private val defaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null, +) : USymbolicCollectionIdWithContextMemory>(contextMemory), + USymbolicMapLengthId> { + override fun UContext.mkReading( + collection: USymbolicCollection, UHeapRef, USizeSort>, + key: UHeapRef + ): UExpr = mkInputMapLengthReading(collection, key) + + override fun UContext.mkLValue( + key: UHeapRef + ): ULValue<*, USizeSort> = UMapLengthLValue(key, mapType) + + override fun keyMapper( + transformer: UTransformer, + ): KeyTransformer = { transformer.apply(it) } + + override fun map(composer: UComposer): UInputMapLengthId { + check(contextMemory == null) { "contextMemory is not null in composition" } + val composedDefaultValue = composer.compose(sort.sampleUValue()) + return UInputMapLengthId(mapType, sort, composedDefaultValue, composer.memory.toWritableMemory()) + } + + override fun keyInfo() = UHeapRefKeyInfo + + override fun rebindKey(key: UHeapRef): DecomposedKey<*, USizeSort>? = when (key) { + is UConcreteHeapRef -> DecomposedKey( + UAllocatedMapLengthId( + mapType, + key.address, + sort, + defaultValue, + contextMemory + ), + Unit + ) + + else -> null + } + + override fun emptyRegion(): USymbolicCollection, UHeapRef, USizeSort> = + USymbolicCollection(this, UFlatUpdates(keyInfo())) + + override fun toString(): String = "length<$mapType>()" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UInputMapLengthId<*> + + return mapType == other.mapType + } + + override fun hashCode(): Int = mapType.hashCode() +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/Expressions.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/Expressions.kt new file mode 100644 index 000000000..4ca2c9a60 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/Expressions.kt @@ -0,0 +1,80 @@ +package org.usvm.collection.map.primitive + +import io.ksmt.cache.hash +import io.ksmt.cache.structurallyEqual +import io.ksmt.expr.KExpr +import io.ksmt.expr.printer.ExpressionPrinter +import io.ksmt.expr.transformer.KTransformerBase +import org.usvm.UCollectionReading +import org.usvm.UContext +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.UNullRef +import org.usvm.USort +import org.usvm.UTransformer +import org.usvm.asTypedTransformer +import org.usvm.collection.map.USymbolicMapKey +import org.usvm.util.Region + +class UAllocatedMapReading> internal constructor( + ctx: UContext, + collection: UAllocatedMap, + val key: UExpr, +) : UCollectionReading, UExpr, Sort>(ctx, collection) { + + override fun accept(transformer: KTransformerBase): KExpr { + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } + return transformer.asTypedTransformer().transform(this) + } + + override fun internEquals(other: Any): Boolean = + structurallyEqual( + other, + { collection }, + { key }, + ) + + override fun internHashCode(): Int = hash(collection, key) + + override fun print(printer: ExpressionPrinter) { + printer.append(collection.toString()) + printer.append("[") + printer.append(key) + printer.append("]") + } +} + +class UInputMapReading> internal constructor( + ctx: UContext, + collection: UInputMap, + val address: UHeapRef, + val key: UExpr +) : UCollectionReading, USymbolicMapKey, Sort>(ctx, collection) { + init { + require(address !is UNullRef) + } + + override fun accept(transformer: KTransformerBase): KExpr { + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } + return transformer.asTypedTransformer().transform(this) + } + + override fun internEquals(other: Any): Boolean = + structurallyEqual( + other, + { collection }, + { address }, + { key }, + ) + + override fun internHashCode(): Int = hash(collection, address, key) + + override fun print(printer: ExpressionPrinter) { + printer.append(collection.toString()) + printer.append("[") + printer.append(address) + printer.append(", ") + printer.append(key) + printer.append("]") + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/UMapModelRegion.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/UMapModelRegion.kt new file mode 100644 index 000000000..72f16c4c7 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/UMapModelRegion.kt @@ -0,0 +1,41 @@ +package org.usvm.collection.map.primitive + +import io.ksmt.solver.KModel +import org.usvm.UExpr +import org.usvm.USort +import org.usvm.collection.map.USymbolicMapKey +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.model.AddressesMapping +import org.usvm.model.modelEnsureConcreteInputRef +import org.usvm.sampleUValue +import org.usvm.solver.UCollectionDecoder +import org.usvm.util.Region + +abstract class UMapModelRegion>( + private val regionId: UMapRegionId +) : UReadOnlyMemoryRegion, ValueSort> { + val defaultValue by lazy { regionId.sort.sampleUValue() } + + abstract val inputMap: UReadOnlyMemoryRegion, ValueSort>? + + override fun read(key: UMapEntryLValue): UExpr { + val mapRef = modelEnsureConcreteInputRef(key.mapRef) ?: return defaultValue + return inputMap?.read(mapRef to key.mapKey) ?: defaultValue + } +} + +class UMapLazyModelRegion>( + regionId: UMapRegionId, + private val model: KModel, + private val addressesMapping: AddressesMapping, + private val inputMapDecoder: UCollectionDecoder, ValueSort>? +) : UMapModelRegion(regionId) { + override val inputMap: UReadOnlyMemoryRegion, ValueSort>? by lazy { + inputMapDecoder?.decodeCollection(model, addressesMapping) + } +} + +class UMapEagerModelRegion>( + regionId: UMapRegionId, + override val inputMap: UReadOnlyMemoryRegion, ValueSort>? +) : UMapModelRegion(regionId) diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/UMapRegion.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/UMapRegion.kt new file mode 100644 index 000000000..ee531e306 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/UMapRegion.kt @@ -0,0 +1,138 @@ +package org.usvm.collection.map.primitive + +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentMapOf +import org.usvm.UBoolExpr +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USort +import org.usvm.memory.USymbolicCollection +import org.usvm.collection.map.USymbolicMapKey +import org.usvm.memory.ULValue +import org.usvm.memory.UMemoryRegion +import org.usvm.memory.UMemoryRegionId +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.memory.foldHeapRef +import org.usvm.memory.map +import org.usvm.uctx +import org.usvm.util.Region + +data class UMapEntryLValue>( + val keySort: KeySort, + override val sort: ValueSort, + val mapRef: UHeapRef, + val mapKey: UExpr, + val mapType: MapType, + val keyInfo: USymbolicCollectionKeyInfo, Reg> +) : ULValue, ValueSort> { + + override val memoryRegionId: UMemoryRegionId, ValueSort> = + UMapRegionId(keySort, sort, mapType, keyInfo) + + override val key: UMapEntryLValue + get() = this +} + +data class UMapRegionId>( + val keySort: KeySort, + override val sort: ValueSort, + val mapType: MapType, + val keyInfo: USymbolicCollectionKeyInfo, Reg> +) : UMemoryRegionId, ValueSort> { + override fun emptyRegion(): UMemoryRegion, ValueSort> = + UMapMemoryRegion(keySort, sort, mapType, keyInfo) +} + +typealias UAllocatedMap = + USymbolicCollection, UExpr, ValueSort> + +typealias UInputMap = + USymbolicCollection, USymbolicMapKey, ValueSort> + +interface UMapRegion> + : UMemoryRegion, ValueSort> + +internal class UMapMemoryRegion>( + private val keySort: KeySort, + private val valueSort: ValueSort, + private val mapType: MapType, + private val keyInfo: USymbolicCollectionKeyInfo, Reg>, + private var allocatedMaps: PersistentMap, UAllocatedMap> = persistentMapOf(), + private var inputMap: UInputMap? = null, +) : UMapRegion { + init { + check(keySort != keySort.uctx.addressSort) { + "Ref map must be used to handle maps with ref keys" + } + } + + private fun getAllocatedMap(id: UAllocatedMapId) = + allocatedMaps[id] ?: id.emptyRegion() + + private fun updateAllocatedMap( + id: UAllocatedMapId, + updatedMap: UAllocatedMap + ) = UMapMemoryRegion( + keySort, + valueSort, + mapType, + keyInfo, + allocatedMaps.put(id, updatedMap), + inputMap + ) + + private fun getInputMap(): UInputMap { + if (inputMap == null) + inputMap = UInputMapId(keySort, valueSort, mapType, keyInfo).emptyRegion() + return inputMap!! + } + + private fun updateInputMap( + updatedMap: UInputMap + ) = UMapMemoryRegion( + keySort, + valueSort, + mapType, + keyInfo, + allocatedMaps, + updatedMap + ) + + override fun read(key: UMapEntryLValue): UExpr = + key.mapRef.map( + { concreteRef -> + val id = UAllocatedMapId(keySort, valueSort, mapType, keyInfo, concreteRef.address) + getAllocatedMap(id).read(key.mapKey) + }, + { symbolicRef -> + getInputMap().read(symbolicRef to key.mapKey) + } + ) + + override fun write( + key: UMapEntryLValue, + value: UExpr, + guard: UBoolExpr + ) = writeNonRefKeyMap(key, value, guard) + + private fun writeNonRefKeyMap( + key: UMapEntryLValue, + value: UExpr, + initialGuard: UBoolExpr + ) = foldHeapRef( + ref = key.mapRef, + initial = this, + initialGuard = initialGuard, + blockOnConcrete = { region, (concreteRef, guard) -> + val id = UAllocatedMapId(keySort, valueSort, mapType, keyInfo, concreteRef.address) + val map = region.getAllocatedMap(id) + val newMap = map.write(key.mapKey, value, guard) + region.updateAllocatedMap(id, newMap) + }, + blockOnSymbolic = { region, (symbolicRef, guard) -> + val map = region.getInputMap() + val newMap = map.write(symbolicRef to key.mapKey, value, guard) + region.updateInputMap(newMap) + } + ) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/UMapRegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/UMapRegionTranslator.kt new file mode 100644 index 000000000..2e08b054e --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/UMapRegionTranslator.kt @@ -0,0 +1,189 @@ +package org.usvm.collection.map.primitive + +import io.ksmt.KContext +import io.ksmt.expr.KExpr +import io.ksmt.solver.KModel +import io.ksmt.sort.KArray2Sort +import io.ksmt.sort.KArraySort +import io.ksmt.utils.mkConst +import org.usvm.UAddressSort +import org.usvm.UConcreteHeapAddress +import org.usvm.UConcreteHeapRef +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USort +import org.usvm.collection.map.USymbolicMapKey +import org.usvm.memory.URangedUpdateNode +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.memory.USymbolicCollection +import org.usvm.model.UMemory2DArray +import org.usvm.solver.U1DUpdatesTranslator +import org.usvm.solver.U2DUpdatesTranslator +import org.usvm.solver.UCollectionDecoder +import org.usvm.solver.UExprTranslator +import org.usvm.solver.URegionDecoder +import org.usvm.solver.URegionTranslator +import org.usvm.uctx +import org.usvm.util.Region +import java.util.IdentityHashMap + +class UMapRegionDecoder>( + private val regionId: UMapRegionId, + private val exprTranslator: UExprTranslator<*> +) : URegionDecoder, ValueSort> { + private val allocatedRegions = + mutableMapOf>() + + private var inputRegion: UInputMapTranslator? = null + + fun allocatedMapTranslator( + collectionId: UAllocatedMapId + ): URegionTranslator, UExpr, ValueSort> = + allocatedRegions.getOrPut(collectionId.address) { + check( + collectionId.mapType == regionId.mapType + && collectionId.keySort == regionId.keySort + && collectionId.sort == regionId.sort + ) { + "Unexpected collection: $collectionId" + } + + UAllocatedMapTranslator(collectionId, exprTranslator) + } + + fun inputMapTranslator( + collectionId: UInputMapId + ): URegionTranslator, USymbolicMapKey, ValueSort> { + if (inputRegion == null) { + check( + collectionId.mapType == regionId.mapType + && collectionId.keySort == regionId.keySort + && collectionId.sort == regionId.sort + ) { + "Unexpected collection: $collectionId" + } + + inputRegion = UInputMapTranslator(collectionId, exprTranslator) + } + return inputRegion!! + } + + override fun decodeLazyRegion( + model: KModel, + mapping: Map + ) = UMapLazyModelRegion(regionId, model, mapping, inputRegion) +} + +private class UAllocatedMapTranslator>( + private val collectionId: UAllocatedMapId, + exprTranslator: UExprTranslator<*> +) : URegionTranslator, UExpr, ValueSort> { + private val initialValue = with(collectionId.sort.uctx) { + val sort = mkArraySort(collectionId.keySort, collectionId.sort) + val translatedDefaultValue = exprTranslator.translate(collectionId.defaultValue) + mkArrayConst(sort, translatedDefaultValue) + } + + private val visitorCache = IdentityHashMap>>() + private val updatesTranslator = UAllocatedMapUpdatesTranslator(exprTranslator, initialValue) + + override fun translateReading( + region: USymbolicCollection, UExpr, ValueSort>, + key: UExpr + ): KExpr { + val translatedCollection = region.updates.accept(updatesTranslator, visitorCache) + return updatesTranslator.visitSelect(translatedCollection, key) + } +} + +private class UInputMapTranslator>( + private val collectionId: UInputMapId, + exprTranslator: UExprTranslator<*> +) : URegionTranslator, USymbolicMapKey, ValueSort>, + UCollectionDecoder, ValueSort> { + private val initialValue = with(collectionId.sort.uctx) { + mkArraySort(addressSort, collectionId.keySort, collectionId.sort).mkConst(collectionId.toString()) + } + + private val visitorCache = IdentityHashMap>>() + private val updatesTranslator = UInputMapUpdatesTranslator(exprTranslator, initialValue) + + override fun translateReading( + region: USymbolicCollection, USymbolicMapKey, ValueSort>, + key: USymbolicMapKey + ): KExpr { + val translatedCollection = region.updates.accept(updatesTranslator, visitorCache) + return updatesTranslator.visitSelect(translatedCollection, key) + } + + override fun decodeCollection( + model: KModel, + mapping: Map + ): UReadOnlyMemoryRegion, ValueSort> = + UMemory2DArray(initialValue, model, mapping) +} + +private class UAllocatedMapUpdatesTranslator( + exprTranslator: UExprTranslator<*>, + initialValue: KExpr> +) : U1DUpdatesTranslator(exprTranslator, initialValue) { + override fun KContext.translateRangedUpdate( + previous: KExpr>, + update: URangedUpdateNode<*, *, UExpr, ValueSort> + ): KExpr> { + +// is UMergeUpdateNode<*, *, *, *, *, *> -> { +// when(update.guard){ +// falseExpr -> previous +// else -> { +// @Suppress("UNCHECKED_CAST") +// update as UMergeUpdateNode, Any?, Any?, KeySort, *, Sort> +// +// val key = mkFreshConst("k", previous.sort.domain) +// +// val from = update.sourceCollection +// +// val keyMapper = from.collectionId.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)) +// mkArrayLambda(key.decl, ite) +// } +// } +// } + TODO("Not yet implemented") + } +} + +private class UInputMapUpdatesTranslator( + exprTranslator: UExprTranslator<*>, + initialValue: KExpr> +) : U2DUpdatesTranslator(exprTranslator, initialValue) { + override fun KContext.translateRangedUpdate( + previous: KExpr>, + update: URangedUpdateNode<*, *, Pair, UExpr>, ValueSort> + ): KExpr> { + // is UMergeUpdateNode<*, *, *, *, *, *> -> { +// when(update.guard){ +// falseExpr -> previous +// else -> { +// @Suppress("UNCHECKED_CAST") +// update as UMergeUpdateNode, Any?, Any?, *, *, Sort> +// +// val key1 = mkFreshConst("k1", previous.sort.domain0) +// val key2 = mkFreshConst("k2", previous.sort.domain1) +// +// val region = update.sourceCollection +// val keyMapper = region.collectionId.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)) +// mkArrayLambda(key1.decl, key2.decl, ite) +// } +// } +// } + TODO("Not yet implemented") + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/USymbolicMapId.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/USymbolicMapId.kt new file mode 100644 index 000000000..ed7fcf812 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/primitive/USymbolicMapId.kt @@ -0,0 +1,211 @@ +package org.usvm.collection.map.primitive + +import io.ksmt.cache.hash +import io.ksmt.expr.KExpr +import org.usvm.UComposer +import org.usvm.UConcreteHeapAddress +import org.usvm.UConcreteHeapRef +import org.usvm.UContext +import org.usvm.UExpr +import org.usvm.USort +import org.usvm.UTransformer +import org.usvm.collection.set.UAllocatedSetId +import org.usvm.collection.set.UInputSetId +import org.usvm.collection.set.USymbolicSetId +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.DecomposedKey +import org.usvm.memory.KeyTransformer +import org.usvm.memory.ULValue +import org.usvm.memory.USymbolicCollectionId +import org.usvm.memory.USymbolicCollectionIdWithContextMemory +import org.usvm.memory.UWritableMemory +import org.usvm.memory.UTreeUpdates +import org.usvm.collection.map.USymbolicMapKey +import org.usvm.collection.map.USymbolicMapKeyInfo +import org.usvm.collection.map.USymbolicMapKeyRegion +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.sampleUValue +import org.usvm.util.Region +import org.usvm.util.emptyRegionTree + +interface USymbolicMapId< + MapType, + Key, + ValueSort : USort, + out KeysSetId : USymbolicSetId, + out MapId : USymbolicMapId> + : USymbolicCollectionId { + val keysSetId: KeysSetId + val mapType: MapType +} + +class UAllocatedMapId> internal constructor( + val keySort: KeySort, + val valueSort: ValueSort, + override val mapType: MapType, + val keyInfo: USymbolicCollectionKeyInfo, Reg>, + val address: UConcreteHeapAddress, + val idDefaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null, +) : USymbolicCollectionIdWithContextMemory< + UExpr, ValueSort, UAllocatedMapId>(contextMemory), + USymbolicMapId, ValueSort, + UAllocatedSetId, Reg>, + UAllocatedMapId> { + + val defaultValue: UExpr by lazy { idDefaultValue ?: valueSort.sampleUValue() } + + override val keysSetId: UAllocatedSetId, Reg> + get() = UAllocatedSetId(keyInfo, contextMemory) + + override val sort: ValueSort get() = valueSort + + override fun UContext.mkReading( + collection: USymbolicCollection, UExpr, ValueSort>, + key: UExpr + ): UExpr { + if (collection.updates.isEmpty()) { + return defaultValue + } + + return mkAllocatedMapReading(collection, key) + } + + override fun UContext.mkLValue( + key: UExpr + ): ULValue<*, ValueSort> = UMapEntryLValue(keySort, sort, mkConcreteHeapRef(address), key, mapType, keyInfo) + + override fun keyMapper( + transformer: UTransformer, + ): KeyTransformer> = { transformer.apply(it) } + + override fun map( + composer: UComposer + ): UAllocatedMapId { + check(contextMemory == null) { "contextMemory is not null in composition" } + val composedDefaultValue = composer.compose(defaultValue) + return UAllocatedMapId( + keySort, valueSort, mapType, keyInfo, address, composedDefaultValue, composer.memory.toWritableMemory() + ) + } + + override fun keyInfo(): USymbolicCollectionKeyInfo, Reg> = keyInfo + + override fun rebindKey(key: UExpr): DecomposedKey<*, ValueSort>? = null + + override fun emptyRegion(): USymbolicCollection, UExpr, ValueSort> { + val updates = UTreeUpdates, Reg, ValueSort>( + updates = emptyRegionTree(), + keyInfo() + ) + return USymbolicCollection(this, updates) + } + + override fun toString(): String = "allocatedMap<$mapType>($address)" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UAllocatedMapId<*, *, *, *> + + if (address != other.address) return false + if (keySort != other.keySort) return false + if (valueSort != other.valueSort) return false + if (mapType != other.mapType) return false + + return true + } + + override fun hashCode(): Int = hash(address, keySort, valueSort, mapType) +} + +class UInputMapId> internal constructor( + val keySort: KeySort, + val valueSort: ValueSort, + override val mapType: MapType, + val keyInfo: USymbolicCollectionKeyInfo, Reg>, + val defaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null, +) : USymbolicCollectionIdWithContextMemory< + USymbolicMapKey, ValueSort, UInputMapId>(contextMemory), + USymbolicMapId, ValueSort, + UInputSetId, USymbolicMapKeyRegion>, + UInputMapId> { + override val keysSetId: UInputSetId, USymbolicMapKeyRegion> + get() = UInputSetId(keyInfo(), contextMemory) + + override val sort: ValueSort get() = valueSort + + override fun UContext.mkReading( + collection: USymbolicCollection, USymbolicMapKey, ValueSort>, + key: USymbolicMapKey + ): UExpr = mkInputMapReading(collection, key.first, key.second) + + override fun UContext.mkLValue( + key: USymbolicMapKey + ): ULValue<*, ValueSort> = UMapEntryLValue(keySort, sort, key.first, key.second, mapType, keyInfo) + + override fun keyMapper( + transformer: UTransformer, + ): KeyTransformer> = { + 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 emptyRegion(): USymbolicCollection, USymbolicMapKey, ValueSort> { + val updates = UTreeUpdates, USymbolicMapKeyRegion, ValueSort>( + updates = emptyRegionTree(), + keyInfo() + ) + return USymbolicCollection(this, updates) + } + + override fun map( + composer: UComposer + ): UInputMapId { + check(contextMemory == null) { "contextMemory is not null in composition" } + val composedDefaultValue = composer.compose(sort.sampleUValue()) + return UInputMapId( + keySort, valueSort, mapType, keyInfo, composedDefaultValue, composer.memory.toWritableMemory() + ) + } + + override fun keyInfo(): USymbolicMapKeyInfo = USymbolicMapKeyInfo(keyInfo) + + override fun rebindKey(key: USymbolicMapKey): DecomposedKey<*, ValueSort>? = + when (val heapRef = key.first) { + is UConcreteHeapRef -> DecomposedKey( + UAllocatedMapId( + keySort, + sort, + mapType, + keyInfo, + heapRef.address, + defaultValue, + contextMemory + ), key.second + ) + + else -> null + } + + override fun toString(): String = "inputMap<$mapType>()" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UInputMapId<*, *, *, *> + + if (keySort != other.keySort) return false + if (valueSort != other.valueSort) return false + if (mapType != other.mapType) return false + + return true + } + + override fun hashCode(): Int = + hash(keySort, valueSort, mapType) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/Expressions.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/Expressions.kt new file mode 100644 index 000000000..84c897597 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/Expressions.kt @@ -0,0 +1,109 @@ +package org.usvm.collection.map.ref + +import io.ksmt.cache.hash +import io.ksmt.cache.structurallyEqual +import io.ksmt.expr.KExpr +import io.ksmt.expr.printer.ExpressionPrinter +import io.ksmt.expr.transformer.KTransformerBase +import org.usvm.UAddressSort +import org.usvm.UCollectionReading +import org.usvm.UContext +import org.usvm.UHeapRef +import org.usvm.UNullRef +import org.usvm.USort +import org.usvm.UTransformer +import org.usvm.asTypedTransformer +import org.usvm.collection.map.USymbolicMapKey + +class UAllocatedRefMapWithInputKeysReading internal constructor( + ctx: UContext, + collection: UAllocatedRefMapWithInputKeys, + val keyRef: UHeapRef, +) : UCollectionReading, UHeapRef, Sort>(ctx, collection) { + + override fun accept(transformer: KTransformerBase): KExpr { + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } + return transformer.asTypedTransformer().transform(this) + } + + override fun internEquals(other: Any): Boolean = + structurallyEqual( + other, + { collection }, + { keyRef }, + ) + + override fun internHashCode(): Int = hash(collection, keyRef) + + override fun print(printer: ExpressionPrinter) { + printer.append(collection.toString()) + printer.append("[") + printer.append(keyRef) + printer.append("]") + } +} + +class UInputRefMapWithAllocatedKeysReading internal constructor( + ctx: UContext, + collection: UInputRefMapWithAllocatedKeys, + val mapRef: UHeapRef, +) : UCollectionReading, UHeapRef, Sort>(ctx, collection) { + + override fun accept(transformer: KTransformerBase): KExpr { + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } + return transformer.asTypedTransformer().transform(this) + } + + override fun internEquals(other: Any): Boolean = + structurallyEqual( + other, + { collection }, + { mapRef }, + ) + + override fun internHashCode(): Int = hash(collection, mapRef) + + override fun print(printer: ExpressionPrinter) { + printer.append(collection.toString()) + printer.append("(") + printer.append(mapRef) + printer.append(")") + } +} + +class UInputRefMapWithInputKeysReading internal constructor( + ctx: UContext, + collection: UInputRefMap, + val mapRef: UHeapRef, + val keyRef: UHeapRef +) : UCollectionReading, + USymbolicMapKey, Sort>(ctx, collection) { + init { + require(mapRef !is UNullRef) + } + + override fun accept(transformer: KTransformerBase): KExpr { + require(transformer is UTransformer<*>) { "Expected a UTransformer, but got: $transformer" } + return transformer.asTypedTransformer().transform(this) + } + + override fun internEquals(other: Any): Boolean = + structurallyEqual( + other, + { collection }, + { mapRef }, + { keyRef } + ) + + override fun internHashCode(): Int = hash(collection, mapRef, keyRef) + + override fun print(printer: ExpressionPrinter) { + printer.append(collection.toString()) + printer.append("(") + printer.append(mapRef) + printer.append(")") + printer.append("[") + printer.append(keyRef) + printer.append("]") + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/SymbolicRefMapRegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/SymbolicRefMapRegionTranslator.kt new file mode 100644 index 000000000..d078b090b --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/SymbolicRefMapRegionTranslator.kt @@ -0,0 +1,215 @@ +package org.usvm.collection.map.ref + +import io.ksmt.KContext +import io.ksmt.expr.KExpr +import io.ksmt.solver.KModel +import io.ksmt.sort.KArray2Sort +import io.ksmt.sort.KArraySort +import io.ksmt.utils.mkConst +import org.usvm.UAddressSort +import org.usvm.UConcreteHeapAddress +import org.usvm.UConcreteHeapRef +import org.usvm.UHeapRef +import org.usvm.USort +import org.usvm.collection.map.USymbolicMapKey +import org.usvm.memory.URangedUpdateNode +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.memory.USymbolicCollection +import org.usvm.model.UMemory2DArray +import org.usvm.solver.U1DUpdatesTranslator +import org.usvm.solver.U2DUpdatesTranslator +import org.usvm.solver.UCollectionDecoder +import org.usvm.solver.UExprTranslator +import org.usvm.solver.URegionDecoder +import org.usvm.solver.URegionTranslator +import org.usvm.uctx +import java.util.IdentityHashMap + +class URefMapRegionDecoder( + private val regionId: URefMapRegionId, + private val exprTranslator: UExprTranslator<*> +) : URegionDecoder, ValueSort> { + private val allocatedWithInputKeysRegions = + mutableMapOf>() + + private val inputWithAllocatedKeysRegions = + mutableMapOf>() + + private var inputRegion: UInputRefMapTranslator? = null + + fun allocatedRefMapWithInputKeysTranslator( + collectionId: UAllocatedRefMapWithInputKeysId + ): URegionTranslator, UHeapRef, ValueSort> = + allocatedWithInputKeysRegions.getOrPut(collectionId.mapAddress) { + check(collectionId.mapType == regionId.mapType && collectionId.sort == regionId.sort) { + "Unexpected collection: $collectionId" + } + + UAllocatedRefMapWithInputKeysTranslator(collectionId, exprTranslator) + } + + fun inputRefMapWithAllocatedKeysTranslator( + collectionId: UInputRefMapWithAllocatedKeysId + ): URegionTranslator, UHeapRef, ValueSort> = + inputWithAllocatedKeysRegions.getOrPut(collectionId.keyAddress) { + check(collectionId.mapType == regionId.mapType && collectionId.sort == regionId.sort) { + "Unexpected collection: $collectionId" + } + + UInputRefMapWithAllocatedKeysTranslator(collectionId, exprTranslator) + } + + fun inputRefMapTranslator( + collectionId: UInputRefMapWithInputKeysId + ): URegionTranslator, USymbolicMapKey, ValueSort> { + if (inputRegion == null) { + check(collectionId.mapType == regionId.mapType && collectionId.sort == regionId.sort) { + "Unexpected collection: $collectionId" + } + + inputRegion = UInputRefMapTranslator(collectionId, exprTranslator) + } + return inputRegion!! + } + + override fun decodeLazyRegion( + model: KModel, + mapping: Map + ) = URefMapLazyModelRegion(regionId, model, mapping, inputRegion) +} + +private class UAllocatedRefMapWithInputKeysTranslator( + private val collectionId: UAllocatedRefMapWithInputKeysId, + exprTranslator: UExprTranslator<*> +) : URegionTranslator, UHeapRef, ValueSort> { + private val initialValue = with(collectionId.sort.uctx) { + val sort = mkArraySort(addressSort, collectionId.sort) + val translatedDefaultValue = exprTranslator.translate(collectionId.defaultValue) + mkArrayConst(sort, translatedDefaultValue) + } + + private val visitorCache = IdentityHashMap>>() + private val updatesTranslator = UAllocatedRefMapUpdatesTranslator(exprTranslator, initialValue) + + override fun translateReading( + region: USymbolicCollection, UHeapRef, ValueSort>, + key: UHeapRef + ): KExpr { + val translatedCollection = region.updates.accept(updatesTranslator, visitorCache) + return updatesTranslator.visitSelect(translatedCollection, key) + } +} + +private class UInputRefMapWithAllocatedKeysTranslator( + private val collectionId: UInputRefMapWithAllocatedKeysId, + exprTranslator: UExprTranslator<*> +) : URegionTranslator, UHeapRef, ValueSort> { + private val initialValue = with(collectionId.sort.uctx) { + val sort = mkArraySort(addressSort, collectionId.sort) + val translatedDefaultValue = exprTranslator.translate(collectionId.defaultValue) + mkArrayConst(sort, translatedDefaultValue) + } + + private val visitorCache = IdentityHashMap>>() + private val updatesTranslator = UAllocatedRefMapUpdatesTranslator(exprTranslator, initialValue) + + override fun translateReading( + region: USymbolicCollection, UHeapRef, ValueSort>, + key: UHeapRef + ): KExpr { + val translatedCollection = region.updates.accept(updatesTranslator, visitorCache) + return updatesTranslator.visitSelect(translatedCollection, key) + } +} + +private class UInputRefMapTranslator( + private val collectionId: UInputRefMapWithInputKeysId, + exprTranslator: UExprTranslator<*> +) : URegionTranslator, USymbolicMapKey, ValueSort>, + UCollectionDecoder, ValueSort> { + private val initialValue = with(collectionId.sort.uctx) { + mkArraySort(addressSort, addressSort, collectionId.sort).mkConst(collectionId.toString()) + } + + private val visitorCache = IdentityHashMap>>() + private val updatesTranslator = UInputRefMapUpdatesTranslator(exprTranslator, initialValue) + + override fun translateReading( + region: USymbolicCollection, USymbolicMapKey, ValueSort>, + key: USymbolicMapKey + ): KExpr { + val translatedCollection = region.updates.accept(updatesTranslator, visitorCache) + return updatesTranslator.visitSelect(translatedCollection, key) + } + + override fun decodeCollection( + model: KModel, + mapping: Map + ): UReadOnlyMemoryRegion, ValueSort> = + UMemory2DArray(initialValue, model, mapping) +} + +private class UAllocatedRefMapUpdatesTranslator( + exprTranslator: UExprTranslator<*>, + initialValue: KExpr> +) : U1DUpdatesTranslator(exprTranslator, initialValue) { + override fun KContext.translateRangedUpdate( + previous: KExpr>, + update: URangedUpdateNode<*, *, UHeapRef, ValueSort> + ): KExpr> { + +// is UMergeUpdateNode<*, *, *, *, *, *> -> { +// when(update.guard){ +// falseExpr -> previous +// else -> { +// @Suppress("UNCHECKED_CAST") +// update as UMergeUpdateNode, Any?, Any?, KeySort, *, Sort> +// +// val key = mkFreshConst("k", previous.sort.domain) +// +// val from = update.sourceCollection +// +// val keyMapper = from.collectionId.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)) +// mkArrayLambda(key.decl, ite) +// } +// } +// } + TODO("Not yet implemented") + } +} + +private class UInputRefMapUpdatesTranslator( + exprTranslator: UExprTranslator<*>, + initialValue: KExpr> +) : U2DUpdatesTranslator(exprTranslator, initialValue) { + override fun KContext.translateRangedUpdate( + previous: KExpr>, + update: URangedUpdateNode<*, *, Pair, ValueSort> + ): KExpr> { + // is UMergeUpdateNode<*, *, *, *, *, *> -> { +// when(update.guard){ +// falseExpr -> previous +// else -> { +// @Suppress("UNCHECKED_CAST") +// update as UMergeUpdateNode, Any?, Any?, *, *, Sort> +// +// val key1 = mkFreshConst("k1", previous.sort.domain0) +// val key2 = mkFreshConst("k2", previous.sort.domain1) +// +// val region = update.sourceCollection +// val keyMapper = region.collectionId.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)) +// mkArrayLambda(key1.decl, key2.decl, ite) +// } +// } +// } + TODO("Not yet implemented") + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/URefMapModelRegion.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/URefMapModelRegion.kt new file mode 100644 index 000000000..d6470e704 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/URefMapModelRegion.kt @@ -0,0 +1,41 @@ +package org.usvm.collection.map.ref + +import io.ksmt.solver.KModel +import org.usvm.UAddressSort +import org.usvm.UExpr +import org.usvm.USort +import org.usvm.collection.map.USymbolicMapKey +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.model.AddressesMapping +import org.usvm.model.modelEnsureConcreteInputRef +import org.usvm.sampleUValue +import org.usvm.solver.UCollectionDecoder + +abstract class URefMapModelRegion( + private val regionId: URefMapRegionId +) : UReadOnlyMemoryRegion, ValueSort> { + val defaultValue by lazy { regionId.sort.sampleUValue() } + + abstract val inputMap: UReadOnlyMemoryRegion, ValueSort>? + + override fun read(key: URefMapEntryLValue): UExpr { + val mapRef = modelEnsureConcreteInputRef(key.mapRef) ?: return defaultValue + return inputMap?.read(mapRef to key.mapKey) ?: defaultValue + } +} + +class URefMapLazyModelRegion( + regionId: URefMapRegionId, + private val model: KModel, + private val addressesMapping: AddressesMapping, + private val inputMapDecoder: UCollectionDecoder, ValueSort>? +) : URefMapModelRegion(regionId) { + override val inputMap: UReadOnlyMemoryRegion, ValueSort>? by lazy { + inputMapDecoder?.decodeCollection(model, addressesMapping) + } +} + +class URefMapEagerModelRegion( + regionId: URefMapRegionId, + override val inputMap: UReadOnlyMemoryRegion, ValueSort>? +) : URefMapModelRegion(regionId) diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/URefMapRegion.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/URefMapRegion.kt new file mode 100644 index 000000000..8dcb41263 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/URefMapRegion.kt @@ -0,0 +1,481 @@ +package org.usvm.collection.map.ref + +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentMapOf +import org.usvm.UAddressSort +import org.usvm.UBoolExpr +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USort +import org.usvm.collection.map.USymbolicMapKey +import org.usvm.collection.set.USetRegionId +import org.usvm.memory.ULValue +import org.usvm.memory.UMemoryRegion +import org.usvm.memory.UMemoryRegionId +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.foldHeapRef +import org.usvm.memory.guardedWrite +import org.usvm.memory.map + +data class URefMapEntryLValue( + override val sort: ValueSort, + val mapRef: UHeapRef, + val mapKey: UHeapRef, + val mapType: MapType +) : ULValue, ValueSort> { + override val memoryRegionId: UMemoryRegionId, ValueSort> = + URefMapRegionId(sort, mapType) + + override val key: URefMapEntryLValue + get() = this +} + +data class URefMapRegionId( + override val sort: ValueSort, + val mapType: MapType, +) : UMemoryRegionId, ValueSort> { + override fun emptyRegion(): UMemoryRegion, ValueSort> = + URefMapMemoryRegion(sort, mapType) +} + +interface URefMapRegion + : UMemoryRegion, ValueSort> { + fun merge( + srcRef: UHeapRef, + dstRef: UHeapRef, + mapType: MapType, + sort: ValueSort, + keySet: USetRegionId, + guard: UBoolExpr + ): URefMapRegion +} + +typealias UAllocatedRefMapWithInputKeys = + USymbolicCollection, UHeapRef, ValueSort> + +typealias UInputRefMapWithAllocatedKeys = + USymbolicCollection, UHeapRef, ValueSort> + +typealias UInputRefMap = + USymbolicCollection, USymbolicMapKey, ValueSort> + +internal class URefMapMemoryRegion( + private val valueSort: ValueSort, + private val mapType: MapType, + private var allocatedMapWithAllocatedKeys: PersistentMap, UExpr> = persistentMapOf(), + private var inputMapWithAllocatedKeys: PersistentMap, UInputRefMapWithAllocatedKeys> = persistentMapOf(), + private var allocatedMapWithInputKeys: PersistentMap, UAllocatedRefMapWithInputKeys> = persistentMapOf(), + private var inputMapWithInputKeys: UInputRefMap? = null, +) : URefMapRegion { + + private fun updateAllocatedMapWithAllocatedKeys( + updated: PersistentMap, UExpr> + ) = URefMapMemoryRegion( + valueSort, + mapType, + updated, + inputMapWithAllocatedKeys, + allocatedMapWithInputKeys, + inputMapWithInputKeys + ) + + private fun getInputMapWithAllocatedKeys(id: UInputRefMapWithAllocatedKeysId) = + inputMapWithAllocatedKeys[id] ?: id.emptyRegion() + + private fun updateInputMapWithAllocatedKeys( + id: UInputRefMapWithAllocatedKeysId, + updatedMap: UInputRefMapWithAllocatedKeys + ) = URefMapMemoryRegion( + valueSort, + mapType, + allocatedMapWithAllocatedKeys, + inputMapWithAllocatedKeys.put(id, updatedMap), + allocatedMapWithInputKeys, + inputMapWithInputKeys + ) + + private fun getAllocatedMapWithInputKeys(id: UAllocatedRefMapWithInputKeysId) = + allocatedMapWithInputKeys[id] ?: id.emptyRegion() + + private fun updateAllocatedMapWithInputKeys( + id: UAllocatedRefMapWithInputKeysId, + updatedMap: UAllocatedRefMapWithInputKeys + ) = URefMapMemoryRegion( + valueSort, + mapType, + allocatedMapWithAllocatedKeys, + inputMapWithAllocatedKeys, + allocatedMapWithInputKeys.put(id, updatedMap), + inputMapWithInputKeys + ) + + private fun getInputMapWithInputKeys(): UInputRefMap { + if (inputMapWithInputKeys == null) + inputMapWithInputKeys = UInputRefMapWithInputKeysId( + valueSort, mapType + ).emptyRegion() + return inputMapWithInputKeys!! + } + + private fun updateInputMapWithInputKeys(updatedMap: UInputRefMap) = + URefMapMemoryRegion( + valueSort, + mapType, + allocatedMapWithAllocatedKeys, + inputMapWithAllocatedKeys, + allocatedMapWithInputKeys, + updatedMap + ) + + override fun read(key: URefMapEntryLValue): UExpr = + key.mapRef.map( + { concreteRef -> + key.mapKey.map( + { concreteKey -> + val id = UAllocatedRefMapWithAllocatedKeysId( + valueSort, mapType, concreteRef.address, concreteKey.address + ) + allocatedMapWithAllocatedKeys[id] ?: id.defaultValue + }, + { symbolicKey -> + val id = UAllocatedRefMapWithInputKeysId( + valueSort, mapType, concreteRef.address + ) + getAllocatedMapWithInputKeys(id).read(symbolicKey) + } + ) + }, + { symbolicRef -> + key.mapKey.map( + { concreteKey -> + val id = UInputRefMapWithAllocatedKeysId( + valueSort, mapType, concreteKey.address + ) + getInputMapWithAllocatedKeys(id).read(symbolicRef) + }, + { symbolicKey -> + getInputMapWithInputKeys().read(symbolicRef to symbolicKey) + } + ) + } + ) + + override fun write( + key: URefMapEntryLValue, + value: UExpr, + guard: UBoolExpr + ) = foldHeapRef( + ref = key.mapRef, + initial = this, + initialGuard = guard, + blockOnConcrete = { mapRegion, (concreteMapRef, mapGuard) -> + foldHeapRef( + ref = key.mapKey, + initial = mapRegion, + initialGuard = mapGuard, + blockOnConcrete = { region, (concreteKeyRef, guard) -> + val id = UAllocatedRefMapWithAllocatedKeysId( + valueSort, mapType, concreteMapRef.address, concreteKeyRef.address + ) + val newMap = region.allocatedMapWithAllocatedKeys.guardedWrite(id, value, guard) { id.defaultValue } + region.updateAllocatedMapWithAllocatedKeys(newMap) + }, + blockOnSymbolic = { region, (symbolicKeyRef, guard) -> + val id = UAllocatedRefMapWithInputKeysId( + valueSort, mapType, concreteMapRef.address + ) + val map = region.getAllocatedMapWithInputKeys(id) + val newMap = map.write(symbolicKeyRef, value, guard) + region.updateAllocatedMapWithInputKeys(id, newMap) + } + ) + }, + blockOnSymbolic = { mapRegion, (symbolicMapRef, mapGuard) -> + foldHeapRef( + ref = key.mapKey, + initial = mapRegion, + initialGuard = mapGuard, + blockOnConcrete = { region, (concreteKeyRef, guard) -> + val id = UInputRefMapWithAllocatedKeysId(valueSort, mapType, concreteKeyRef.address) + val map = region.getInputMapWithAllocatedKeys(id) + val newMap = map.write(symbolicMapRef, value, guard) + region.updateInputMapWithAllocatedKeys(id, newMap) + }, + blockOnSymbolic = { region, (symbolicKeyRef, guard) -> + val map = region.getInputMapWithInputKeys() + val newMap = map.write(symbolicMapRef to symbolicKeyRef, value, guard) + region.updateInputMapWithInputKeys(newMap) + } + ) + } + ) + + override fun merge( + srcRef: UHeapRef, + dstRef: UHeapRef, + mapType: MapType, + sort: ValueSort, + keySet: USetRegionId, + guard: UBoolExpr + ): URefMapRegion { + TODO("Not yet implemented") + } + + +// override fun , Sort : USort> mergeSymbolicMap( +// descriptor: USymbolicMapDescriptor, +// keyContainsDescriptor: USymbolicMapDescriptor, +// srcRef: UHeapRef, +// dstRef: UHeapRef, +// guard: UBoolExpr +// ) { +// if (descriptor.keySort == descriptor.keySort.uctx.addressSort) { +// @Suppress("UNCHECKED_CAST") +// return mergeSymbolicRefMap( +// descriptor as USymbolicMapDescriptor, +// keyContainsDescriptor as USymbolicMapDescriptor, +// srcRef, +// dstRef, +// guard +// ) +// } +// +// withHeapRef( +// srcRef, +// guard, +// blockOnConcrete = { (srcRef, guard) -> +// val srcRegion = allocatedMapRegion(descriptor, srcRef.address) +// val srcKeyContainsRegion = allocatedMapRegion(keyContainsDescriptor, srcRef.address) +// +// withHeapRef( +// dstRef, +// guard, +// blockOnConcrete = { (dstRef, deepGuard) -> +// val dstRegion = allocatedMapRegion(descriptor, dstRef.address) +// +// val newDstRegion = dstRegion.mergeWithCollection( +// fromCollection = srcRegion, +// guard = deepGuard, +// keyIncludesCheck = UMergeKeyIncludesCheck(srcKeyContainsRegion), +// keyConverter = USymbolicMapMergeAdapter(srcRef, dstRef) { it } +// ) +// +// storeAllocatedMapRegion(descriptor, dstRef.address, newDstRegion) +// }, +// blockOnSymbolic = { (dstRef, deepGuard) -> +// val dstRegion = inputMapRegion(descriptor) +// +// val newDstRegion = dstRegion.mergeWithCollection( +// fromCollection = srcRegion, +// guard = deepGuard, +// keyIncludesCheck = UMergeKeyIncludesCheck(srcKeyContainsRegion), +// keyConverter = USymbolicMapMergeAdapter(srcRef, dstRef) { it.second } +// ) +// +// inputMaps = inputMaps.put(descriptor, newDstRegion.uncheckedCast()) +// }, +// ) +// }, +// blockOnSymbolic = { (srcRef, guard) -> +// val srcRegion = inputMapRegion(descriptor) +// val srcKeyContainsRegion = inputMapRegion(keyContainsDescriptor) +// +// withHeapRef( +// dstRef, +// guard, +// blockOnConcrete = { (dstRef, deepGuard) -> +// val dstRegion = allocatedMapRegion(descriptor, dstRef.address) +// +// val newDstRegion = dstRegion.mergeWithCollection( +// fromCollection = srcRegion, +// guard = deepGuard, +// keyIncludesCheck = UMergeKeyIncludesCheck(srcKeyContainsRegion), +// keyConverter = USymbolicMapMergeAdapter(srcRef, dstRef) { this.srcRef to it } +// ) +// +// storeAllocatedMapRegion(descriptor, dstRef.address, newDstRegion) +// }, +// blockOnSymbolic = { (dstRef, deepGuard) -> +// val dstRegion = inputMapRegion(descriptor) +// +// val newDstRegion = dstRegion.mergeWithCollection( +// fromCollection = srcRegion, +// guard = deepGuard, +// keyIncludesCheck = UMergeKeyIncludesCheck(srcKeyContainsRegion), +// keyConverter = USymbolicMapMergeAdapter(srcRef, dstRef) { this.srcRef to it.second } +// ) +// +// inputMaps = inputMaps.put(descriptor, newDstRegion.uncheckedCast()) +// }, +// ) +// }, +// ) +// } +// +// /** +// * Merge maps with ref keys. +// * +// * Note 1: there are no concrete keys in input maps. +// * Therefore, we can enumerate all possible concrete keys. +// * +// * Note 2: concrete keys can't intersect with symbolic ones. +// * +// * Merge: +// * 1. Merge src symbolic keys into dst symbolic keys using `merge update node`. +// * 2. Merge src concrete keys into dst concrete keys. +// * 2.1 enumerate all concrete keys using map writes. +// * 2.2 write keys into dst with `map.write` operation. +// * */ +// private fun , Sort : USort> mergeSymbolicRefMap( +// descriptor: USymbolicMapDescriptor, +// keyContainsDescriptor: USymbolicMapDescriptor, +// srcRef: UHeapRef, +// dstRef: UHeapRef, +// guard: UBoolExpr +// ) { +// withHeapRef( +// srcRef, +// guard, +// blockOnConcrete = { (srcRef, guard) -> +// val srcSymbolicKeysRegion = allocatedMapRegion( +// descriptor = descriptor, +// address = srcRef.address, +// tag = SymbolicKeyConcreteRefAllocatedMap +// ) +// +// val srcSymbolicKeyContainsRegion = allocatedMapRegion( +// descriptor = keyContainsDescriptor, +// address = srcRef.address, +// tag = SymbolicKeyConcreteRefAllocatedMap +// ) +// +// withHeapRef( +// dstRef, +// guard, +// blockOnConcrete = { (dstRef, deepGuard) -> +// val dstSymbolicKeysRegion = allocatedMapRegion( +// descriptor = descriptor, +// address = dstRef.address, +// tag = SymbolicKeyConcreteRefAllocatedMap +// ) +// +// val mergedSymbolicKeysRegion = dstSymbolicKeysRegion.mergeWithCollection( +// fromCollection = srcSymbolicKeysRegion, +// guard = deepGuard, +// keyIncludesCheck = UMergeKeyIncludesCheck(srcSymbolicKeyContainsRegion), +// keyConverter = USymbolicMapMergeAdapter(srcRef, dstRef) { it } +// ) +// +// storeAllocatedMapRegion( +// descriptor = descriptor, +// address = dstRef.address, +// newRegion = mergedSymbolicKeysRegion, +// tag = SymbolicKeyConcreteRefAllocatedMap +// ) +// }, +// blockOnSymbolic = { (dstRef, deepGuard) -> +// val dstSymbolicKeysRegion = inputMapRegion(descriptor) +// +// val mergedSymbolicKeysRegion = dstSymbolicKeysRegion.mergeWithCollection( +// fromCollection = srcSymbolicKeysRegion, +// guard = deepGuard, +// keyIncludesCheck = UMergeKeyIncludesCheck(srcSymbolicKeyContainsRegion), +// keyConverter = USymbolicMapMergeAdapter(srcRef, dstRef) { it.second } +// ) +// +// inputMaps = inputMaps.put(descriptor, mergedSymbolicKeysRegion.uncheckedCast()) +// }, +// ) +// +// val srcKeysTag = ConcreteTaggedMapDescriptor( +// descriptor = descriptor, +// tag = ConcreteKeyConcreteRefAllocatedMap +// ) +// val possibleSrcConcreteKeys = allocatedMaps[srcKeysTag]?.keys ?: emptySet() +// +// mergeConcreteRefKeys( +// keys = possibleSrcConcreteKeys, +// keyContainsDescriptor = keyContainsDescriptor, +// srcRef = srcRef, +// initialGuard = guard, +// descriptor = descriptor, +// dstRef = dstRef +// ) +// }, +// blockOnSymbolic = { (srcRef, guard) -> +// val srcSymbolicKeysRegion = inputMapRegion(descriptor) +// val srcSymbolicKeysContainsRegion = inputMapRegion(keyContainsDescriptor) +// +// withHeapRef( +// dstRef, +// guard, +// blockOnConcrete = { (dstRef, deepGuard) -> +// val dstSymbolicKeysRegion = allocatedMapRegion( +// descriptor = descriptor, +// address = dstRef.address, +// tag = SymbolicKeyConcreteRefAllocatedMap +// ) +// +// val mergedSymbolicKeysRegion = dstSymbolicKeysRegion.mergeWithCollection( +// fromCollection = srcSymbolicKeysRegion, +// guard = deepGuard, +// keyIncludesCheck = UMergeKeyIncludesCheck(srcSymbolicKeysContainsRegion), +// keyConverter = USymbolicMapMergeAdapter(srcRef, dstRef) { this.srcRef to it } +// ) +// +// storeAllocatedMapRegion( +// descriptor = descriptor, +// address = dstRef.address, +// newRegion = mergedSymbolicKeysRegion, +// tag = SymbolicKeyConcreteRefAllocatedMap +// ) +// }, +// blockOnSymbolic = { (dstRef, deepGuard) -> +// val dstSymbolicKeysRegion = inputMapRegion(descriptor) +// +// val mergedSymbolicKeysRegion = dstSymbolicKeysRegion.mergeWithCollection( +// fromCollection = srcSymbolicKeysRegion, +// guard = deepGuard, +// keyIncludesCheck = UMergeKeyIncludesCheck(srcSymbolicKeysContainsRegion), +// keyConverter = USymbolicMapMergeAdapter(srcRef, dstRef) { this.srcRef to it.second } +// ) +// +// inputMaps = inputMaps.put(descriptor, mergedSymbolicKeysRegion.uncheckedCast()) +// }, +// ) +// +// val srcKeysTag = ConcreteTaggedMapDescriptor( +// descriptor = descriptor, +// tag = ConcreteKeySymbolicRefAllocatedMap +// ) +// val possibleSrcConcreteKeys = allocatedMaps[srcKeysTag]?.keys ?: emptySet() +// +// mergeConcreteRefKeys( +// keys = possibleSrcConcreteKeys, +// keyContainsDescriptor = keyContainsDescriptor, +// srcRef = srcRef, +// initialGuard = guard, +// descriptor = descriptor, +// dstRef = dstRef +// ) +// }, +// ) +// } +// +// private fun , Sort : USort> mergeConcreteRefKeys( +// keys: Set, +// keyContainsDescriptor: USymbolicMapDescriptor, +// srcRef: UHeapRef, +// initialGuard: UBoolExpr, +// descriptor: USymbolicMapDescriptor, +// dstRef: UHeapRef +// ) = keys.forEach { key -> +// val keyRef = ctx.mkConcreteHeapRef(key) +// +// val include = readSymbolicRefMap(keyContainsDescriptor, srcRef, keyRef) +// val keyMergeGuard = ctx.mkAnd(include, initialGuard, flat = false) +// +// val srcValue = readSymbolicRefMap(descriptor, srcRef, keyRef) +// writeSymbolicRefMap(descriptor, dstRef, keyRef, srcValue, keyMergeGuard) +// } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/URefMapRegionApi.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/URefMapRegionApi.kt new file mode 100644 index 000000000..4f0c26403 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/URefMapRegionApi.kt @@ -0,0 +1,27 @@ +package org.usvm.collection.map.ref + +import org.usvm.UAddressSort +import org.usvm.UBoolExpr +import org.usvm.UHeapRef +import org.usvm.USort +import org.usvm.collection.set.USetRegionId +import org.usvm.memory.UWritableMemory + +fun UWritableMemory<*>.refMapMerge( + srcRef: UHeapRef, + dstRef: UHeapRef, + mapType: MapType, + sort: ValueSort, + keySet: USetRegionId, + guard: UBoolExpr +) { + val regionId = URefMapRegionId(sort, mapType) + val region = getRegion(regionId) + + check(region is URefMapRegion) { + "refMapMerge is not applicable to $region" + } + + val newRegion = region.merge(srcRef, dstRef, mapType, sort, keySet, guard) + setRegion(regionId, newRegion) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/USymbolicRefMapId.kt b/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/USymbolicRefMapId.kt new file mode 100644 index 000000000..0fcfeedc4 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/map/ref/USymbolicRefMapId.kt @@ -0,0 +1,404 @@ +package org.usvm.collection.map.ref + +import io.ksmt.cache.hash +import org.usvm.UAddressSort +import org.usvm.UComposer +import org.usvm.UConcreteHeapAddress +import org.usvm.UConcreteHeapRef +import org.usvm.UContext +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USort +import org.usvm.UTransformer +import org.usvm.collection.map.USymbolicMapKey +import org.usvm.collection.map.USymbolicMapKeyInfo +import org.usvm.collection.map.USymbolicMapKeyRegion +import org.usvm.collection.set.UAllocatedSetId +import org.usvm.collection.set.UInputSetId +import org.usvm.collection.set.USymbolicSetId +import org.usvm.memory.DecomposedKey +import org.usvm.memory.KeyTransformer +import org.usvm.memory.ULValue +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.USymbolicCollectionId +import org.usvm.memory.USymbolicCollectionIdWithContextMemory +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.memory.UTreeUpdates +import org.usvm.memory.UWritableMemory +import org.usvm.memory.key.UHeapRefKeyInfo +import org.usvm.memory.key.UHeapRefRegion +import org.usvm.memory.key.USingleKeyInfo +import org.usvm.sampleUValue +import org.usvm.util.emptyRegionTree + +interface USymbolicRefMapId< + MapType, + Key, + ValueSort : USort, + out KeysSetId : USymbolicSetId, + out MapId : USymbolicRefMapId> + : USymbolicCollectionId { + val keysSetId: KeysSetId + val mapType: MapType +} + +class UAllocatedRefMapWithAllocatedKeysId( + override val sort: ValueSort, + override val mapType: MapType, + val mapAddress: UConcreteHeapAddress, + val keyAddress: UConcreteHeapAddress, + val idDefaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null +) : USymbolicCollectionIdWithContextMemory< + Unit, ValueSort, UAllocatedRefMapWithAllocatedKeysId>(contextMemory), + USymbolicRefMapId> { + + val defaultValue: UExpr by lazy { idDefaultValue ?: sort.sampleUValue() } + + override fun rebindKey(key: Unit): DecomposedKey<*, ValueSort>? = null + + override fun toString(): String = "allocatedMap<$mapType>($mapAddress)[$keyAddress]" + + override fun keyInfo(): USymbolicCollectionKeyInfo = USingleKeyInfo + + override fun UContext.mkReading( + collection: USymbolicCollection, Unit, ValueSort>, + key: Unit + ): UExpr { + check(collection.updates.isEmpty()) { "Can't instantiate allocated map reading from non-empty collection" } + return defaultValue + } + + override fun UContext.mkLValue( + key: Unit + ): ULValue<*, ValueSort> = URefMapEntryLValue( + sort, mkConcreteHeapRef(mapAddress), mkConcreteHeapRef(keyAddress), mapType + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UAllocatedRefMapWithAllocatedKeysId<*, *> + + if (sort != other.sort) return false + if (mapType != other.mapType) return false + if (mapAddress != other.mapAddress) return false + if (keyAddress != other.keyAddress) return false + + return true + } + + override fun hashCode(): Int = hash(mapAddress, keyAddress, mapType, sort) + + override val keysSetId: Nothing + get() = TODO() + + override fun emptyRegion() = + error("This should not be called") + + override fun keyMapper(transformer: UTransformer): KeyTransformer = + error("This should not be called") + + override fun map( + composer: UComposer + ) = error("This should not be called") +} + +class UAllocatedRefMapWithInputKeysId( + override val sort: ValueSort, + override val mapType: MapType, + val mapAddress: UConcreteHeapAddress, + val idDefaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null, +) : USymbolicCollectionIdWithContextMemory< + UHeapRef, ValueSort, UAllocatedRefMapWithInputKeysId>(contextMemory), + USymbolicRefMapId, + UAllocatedRefMapWithInputKeysId> { + + val defaultValue: UExpr by lazy { idDefaultValue ?: sort.sampleUValue() } + + override fun rebindKey(key: UHeapRef): DecomposedKey<*, ValueSort>? = + when (key) { + is UConcreteHeapRef -> DecomposedKey( + UAllocatedRefMapWithAllocatedKeysId( + sort, + mapType, + mapAddress, + key.address, + idDefaultValue, + contextMemory + ), + Unit + ) + + else -> null + } + + override fun UContext.mkReading( + collection: USymbolicCollection, UHeapRef, ValueSort>, + key: UHeapRef + ): UExpr { + if (collection.updates.isEmpty()) { + return defaultValue + } + + return mkAllocatedRefMapWithInputKeysReading(collection, key) + } + + override fun UContext.mkLValue(key: UHeapRef): ULValue<*, ValueSort> = + URefMapEntryLValue(sort, mkConcreteHeapRef(mapAddress), key, mapType) + + override val keysSetId: UAllocatedSetId + get() = UAllocatedSetId(UHeapRefKeyInfo, contextMemory) + + override fun keyInfo(): USymbolicCollectionKeyInfo = UHeapRefKeyInfo + + override fun keyMapper( + transformer: UTransformer, + ): KeyTransformer = { transformer.apply(it) } + + override fun map( + composer: UComposer + ): UAllocatedRefMapWithInputKeysId { + check(contextMemory == null) { "contextMemory is not null in composition" } + val composedDefaultValue = composer.compose(defaultValue) + return UAllocatedRefMapWithInputKeysId( + sort, mapType, mapAddress, composedDefaultValue, composer.memory.toWritableMemory() + ) + } + + override fun emptyRegion(): USymbolicCollection, UHeapRef, ValueSort> { + val updates = UTreeUpdates( + updates = emptyRegionTree(), + UHeapRefKeyInfo + ) + return USymbolicCollection(this, updates) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UAllocatedRefMapWithInputKeysId<*, *> + + if (sort != other.sort) return false + if (mapType != other.mapType) return false + if (mapAddress != other.mapAddress) return false + + return true + } + + override fun hashCode(): Int = hash(mapAddress, mapType, sort) + + override fun toString(): String = "allocatedRefMap<$mapType>($mapAddress)" +} + +class UInputRefMapWithAllocatedKeysId( + override val sort: ValueSort, + override val mapType: MapType, + val keyAddress: UConcreteHeapAddress, + val idDefaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null, +) : USymbolicCollectionIdWithContextMemory< + UHeapRef, ValueSort, UInputRefMapWithAllocatedKeysId>(contextMemory), + USymbolicRefMapId, + UInputRefMapWithAllocatedKeysId> { + + val defaultValue: UExpr by lazy { idDefaultValue ?: sort.sampleUValue() } + + override fun rebindKey(key: UHeapRef): DecomposedKey<*, ValueSort>? = + when (key) { + is UConcreteHeapRef -> DecomposedKey( + UAllocatedRefMapWithAllocatedKeysId( + sort, + mapType, + key.address, + keyAddress, + idDefaultValue, + contextMemory + ), + Unit + ) + + else -> null + } + + override fun UContext.mkReading( + collection: USymbolicCollection, UHeapRef, ValueSort>, + key: UHeapRef + ): UExpr { + if (collection.updates.isEmpty()) { + return defaultValue + } + + return mkInputRefMapWithAllocatedKeysReading(collection, key) + } + + override fun UContext.mkLValue(key: UHeapRef): ULValue<*, ValueSort> = + URefMapEntryLValue(sort, key, mkConcreteHeapRef(keyAddress), mapType) + + override val keysSetId: UAllocatedSetId + get() = UAllocatedSetId(UHeapRefKeyInfo, contextMemory) + + override fun keyInfo(): USymbolicCollectionKeyInfo = UHeapRefKeyInfo + + override fun keyMapper( + transformer: UTransformer, + ): KeyTransformer = { transformer.apply(it) } + + override fun map( + composer: UComposer + ): UInputRefMapWithAllocatedKeysId { + check(contextMemory == null) { "contextMemory is not null in composition" } + val composedDefaultValue = composer.compose(defaultValue) + return UInputRefMapWithAllocatedKeysId( + sort, mapType, keyAddress, composedDefaultValue, composer.memory.toWritableMemory() + ) + } + + override fun emptyRegion(): USymbolicCollection, UHeapRef, ValueSort> { + val updates = UTreeUpdates( + updates = emptyRegionTree(), + UHeapRefKeyInfo + ) + return USymbolicCollection(this, updates) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UInputRefMapWithAllocatedKeysId<*, *> + + if (sort != other.sort) return false + if (mapType != other.mapType) return false + if (keyAddress != other.keyAddress) return false + + return true + } + + override fun hashCode(): Int = hash(keyAddress, mapType, sort) + + override fun toString(): String = "inputRefMap<$mapType>()[$keyAddress]" +} + +class UInputRefMapWithInputKeysId( + override val sort: ValueSort, + override val mapType: MapType, + val defaultValue: UExpr? = null, + contextMemory: UWritableMemory<*>? = null, +) : USymbolicCollectionIdWithContextMemory< + USymbolicMapKey, ValueSort, UInputRefMapWithInputKeysId>(contextMemory), + USymbolicRefMapId, ValueSort, + UInputSetId, *>, + UInputRefMapWithInputKeysId> { + + override fun rebindKey(key: USymbolicMapKey): DecomposedKey<*, ValueSort>? { + val mapRef = key.first + val keyRef = key.second + + return when (mapRef) { + is UConcreteHeapRef -> when (keyRef) { + is UConcreteHeapRef -> DecomposedKey( + UAllocatedRefMapWithAllocatedKeysId( + sort, + mapType, + mapRef.address, + keyRef.address, + defaultValue, + contextMemory + ), + Unit + ) + + else -> DecomposedKey( + UAllocatedRefMapWithInputKeysId( + sort, + mapType, + mapRef.address, + defaultValue, + contextMemory + ), + keyRef + ) + } + + else -> when (keyRef) { + is UConcreteHeapRef -> DecomposedKey( + UInputRefMapWithAllocatedKeysId( + sort, + mapType, + keyRef.address, + defaultValue, + contextMemory + ), + mapRef + ) + + else -> null + } + } + } + + override fun UContext.mkReading( + collection: USymbolicCollection, USymbolicMapKey, ValueSort>, + key: USymbolicMapKey + ): UExpr { + return mkInputRefMapWithInputKeysReading(collection, key.first, key.second) + } + + override fun UContext.mkLValue(key: USymbolicMapKey): ULValue<*, ValueSort> = + URefMapEntryLValue(sort, key.first, key.second, mapType) + + override val keysSetId: UInputSetId, *> + get() = UInputSetId(keyInfo(), contextMemory) + + override fun keyInfo(): USymbolicCollectionKeyInfo, *> = + USymbolicMapKeyInfo(UHeapRefKeyInfo) + + override fun keyMapper( + transformer: UTransformer, + ): KeyTransformer> = { + 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 + ): UInputRefMapWithInputKeysId { + check(contextMemory == null) { "contextMemory is not null in composition" } + val composedDefaultValue = composer.compose(sort.sampleUValue()) + return UInputRefMapWithInputKeysId( + sort, mapType, composedDefaultValue, composer.memory.toWritableMemory() + ) + } + + override fun emptyRegion(): USymbolicCollection, USymbolicMapKey, ValueSort> { + val updates = + UTreeUpdates, USymbolicMapKeyRegion, ValueSort>( + updates = emptyRegionTree(), + USymbolicMapKeyInfo(UHeapRefKeyInfo) + ) + return USymbolicCollection(this, updates) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UInputRefMapWithInputKeysId<*, *> + + if (sort != other.sort) return false + if (mapType != other.mapType) return false + + return true + } + + override fun hashCode(): Int = hash(mapType, sort) + + override fun toString(): String = "inputRefMap<$mapType>()" +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/set/USetRegion.kt b/usvm-core/src/main/kotlin/org/usvm/collection/set/USetRegion.kt new file mode 100644 index 000000000..fa312fd18 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/set/USetRegion.kt @@ -0,0 +1,56 @@ +package org.usvm.collection.set + +import org.usvm.UBoolExpr +import org.usvm.UBoolSort +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USort +import org.usvm.memory.ULValue +import org.usvm.memory.UMemoryRegion +import org.usvm.memory.UMemoryRegionId +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.uctx +import org.usvm.util.Region + +data class USetEntryLValue>( + val keySort: KeySort, + val setRef: UHeapRef, + val setKey: UExpr, + val setType: SetType, + val keyInfo: USymbolicCollectionKeyInfo, Reg> +) : ULValue, UBoolSort> { + override val sort: UBoolSort + get() = keySort.uctx.boolSort + + override val memoryRegionId: UMemoryRegionId, UBoolSort> + get() = USetRegionId(keySort, setType, keyInfo) + + override val key: USetEntryLValue + get() = this +} + +data class USetRegionId>( + val keySort: KeySort, + val setType: SetType, + val keyInfo: USymbolicCollectionKeyInfo, Reg> +) : UMemoryRegionId, UBoolSort> { + override val sort: UBoolSort + get() = keySort.uctx.boolSort + + override fun emptyRegion(): UMemoryRegion, UBoolSort> { + TODO("Not yet implemented") + } +} + +interface USetRegion> : + UMemoryRegion, UBoolSort> { + + fun union( + srcRef: UHeapRef, + dstRef: UHeapRef, + type: SetType, + keySort: KeySort, + keyInfo: USymbolicCollectionKeyInfo, Reg>, + guard: UBoolExpr, + ): USetRegion +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/set/USetRegionApi.kt b/usvm-core/src/main/kotlin/org/usvm/collection/set/USetRegionApi.kt new file mode 100644 index 000000000..b71b7db3b --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/set/USetRegionApi.kt @@ -0,0 +1,28 @@ +package org.usvm.collection.set + +import org.usvm.UBoolExpr +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.USort +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.memory.UWritableMemory +import org.usvm.util.Region + +internal fun > UWritableMemory<*>.setUnion( + srcRef: UHeapRef, + dstRef: UHeapRef, + type: SetType, + keySort: KeySort, + keyInfo: USymbolicCollectionKeyInfo, Reg>, + guard: UBoolExpr, +) { + val regionId = USetRegionId(keySort, type, keyInfo) + val region = getRegion(regionId) + + check(region is USetRegion) { + "setUnion is not applicable to $region" + } + + val newRegion = region.union(srcRef, dstRef, type, keySort, keyInfo, guard) + setRegion(regionId, newRegion) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/collection/set/USymbolicSetId.kt b/usvm-core/src/main/kotlin/org/usvm/collection/set/USymbolicSetId.kt new file mode 100644 index 000000000..37276ac42 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/collection/set/USymbolicSetId.kt @@ -0,0 +1,176 @@ +package org.usvm.collection.set + +import io.ksmt.utils.uncheckedCast +import org.usvm.UBoolExpr +import org.usvm.UBoolSort +import org.usvm.UComposer +import org.usvm.UContext +import org.usvm.UExpr +import org.usvm.UTransformer +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.DecomposedKey +import org.usvm.memory.KeyTransformer +import org.usvm.memory.ULValue +import org.usvm.memory.UMemoryUpdatesVisitor +import org.usvm.memory.UPinpointUpdateNode +import org.usvm.memory.URangedUpdateNode +import org.usvm.memory.USymbolicCollectionIdWithContextMemory +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.memory.USymbolicCollectionUpdates +import org.usvm.memory.UUpdateNode +import org.usvm.memory.UWritableMemory +import org.usvm.util.Region +import java.util.IdentityHashMap + + +abstract class USymbolicSetId, out SetId : USymbolicSetId>( + contextMemory: UWritableMemory<*>? +) : USymbolicCollectionIdWithContextMemory(contextMemory) { + + fun defaultRegion(): Reg { + if (contextMemory == null) { + return baseRegion() + } + // TODO: get corresponding collection from contextMemory, recursively eval its region + TODO() + } + + abstract fun baseRegion(): Reg + + private val regionCache = IdentityHashMap>() + + /** + * Returns over-approximation of keys collection set. + */ + @Suppress("UNCHECKED_CAST") + fun > region(updates: USymbolicCollectionUpdates): ResultReg { + val regionBuilder = USetRegionBuilder(this) + val result = updates.accept(regionBuilder, regionCache as MutableMap) + return result as ResultReg + } +} + +class UAllocatedSetId>( + val elementInfo: USymbolicCollectionKeyInfo, + contextMemory: UWritableMemory<*>? +) : USymbolicSetId>(contextMemory) { + + override val sort: UBoolSort + get() = TODO("Not yet implemented") + + override fun baseRegion(): Reg = + elementInfo.bottomRegion() + + override fun UContext.mkReading( + collection: USymbolicCollection, Element, UBoolSort>, + key: Element + ): UExpr { + TODO("Not yet implemented") + } + + override fun UContext.mkLValue( + key: Element + ): ULValue<*, UBoolSort> { + TODO("Not yet implemented") + } + + override fun write(memory: UWritableMemory, key: Element, value: UExpr, guard: UBoolExpr) { + TODO("Not yet implemented") + } + + override fun keyMapper(transformer: UTransformer): KeyTransformer { + TODO("Not yet implemented") + } + + override fun map(composer: UComposer): UAllocatedSetId { + TODO("Not yet implemented") + } + + override fun keyInfo(): USymbolicCollectionKeyInfo { + TODO("Not yet implemented") + } + + override fun emptyRegion(): USymbolicCollection, Element, UBoolSort> { + TODO("Not yet implemented") + } + + override fun rebindKey(key: Element): DecomposedKey<*, UBoolSort>? { + TODO("Not yet implemented") + } +} + +class UInputSetId>( + val elementInfo: USymbolicCollectionKeyInfo, + contextMemory: UWritableMemory<*>? +) : USymbolicSetId>(contextMemory) { + + override val sort: UBoolSort + get() = TODO("Not yet implemented") + + override fun baseRegion(): Reg = + elementInfo.topRegion() + + override fun UContext.mkReading( + collection: USymbolicCollection, Element, UBoolSort>, + key: Element + ): UExpr { + TODO("Not yet implemented") + } + + override fun UContext.mkLValue( + key: Element + ): ULValue<*, UBoolSort> { + TODO("Not yet implemented") + } + + override fun write(memory: UWritableMemory, key: Element, value: UExpr, guard: UBoolExpr) { + TODO("Not yet implemented") + } + + override fun keyMapper(transformer: UTransformer): KeyTransformer { + TODO("Not yet implemented") + } + + override fun map(composer: UComposer): UInputSetId { + TODO("Not yet implemented") + } + + override fun keyInfo(): USymbolicCollectionKeyInfo { + TODO("Not yet implemented") + } + + override fun rebindKey(key: Element): DecomposedKey<*, UBoolSort>? { + TODO("Not yet implemented") + } + + override fun emptyRegion(): USymbolicCollection, Element, UBoolSort> { + TODO("Not yet implemented") + } +} + +private class USetRegionBuilder>( + private val collectionId: USymbolicSetId +) : UMemoryUpdatesVisitor { + + private val keyInfo = collectionId.keyInfo() + + override fun visitSelect(result: Reg, key: Key): UBoolExpr { + error("Unexpected reading") + } + + override fun visitInitialValue(): Reg = + collectionId.defaultRegion() + + override fun visitUpdate(previous: Reg, update: UUpdateNode): Reg = when (update) { + is UPinpointUpdateNode -> { + // TODO: removed keys + val keyReg = keyInfo.keyToRegion(update.key) + previous.union(keyReg.uncheckedCast()) + } + + is URangedUpdateNode<*, *, *, UBoolSort> -> { + val updatedKeys: Reg = update.adapter.region() + previous.union(updatedKeys) + } + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/constraints/TypeConstraints.kt b/usvm-core/src/main/kotlin/org/usvm/constraints/TypeConstraints.kt index 99d243ee7..b63ea46ba 100644 --- a/usvm-core/src/main/kotlin/org/usvm/constraints/TypeConstraints.kt +++ b/usvm-core/src/main/kotlin/org/usvm/constraints/TypeConstraints.kt @@ -16,6 +16,7 @@ import org.usvm.uctx interface UTypeEvaluator { fun evalIsSubtype(ref: UHeapRef, supertype: Type): UBoolExpr fun evalIsSupertype(ref: UHeapRef, subtype: Type): UBoolExpr + fun getTypeStream(ref: UHeapRef): UTypeStream } /** @@ -200,7 +201,7 @@ class UTypeConstraints( /** * @return a type stream corresponding to the [ref]. */ - internal fun getTypeStream(ref: UHeapRef): UTypeStream = + override fun getTypeStream(ref: UHeapRef): UTypeStream = when (ref) { is UConcreteHeapRef -> { val concreteType = concreteRefToType[ref.address] diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/Heap.kt b/usvm-core/src/main/kotlin/org/usvm/memory/Heap.kt deleted file mode 100644 index 3361f29f3..000000000 --- a/usvm-core/src/main/kotlin/org/usvm/memory/Heap.kt +++ /dev/null @@ -1,373 +0,0 @@ -package org.usvm.memory - -import io.ksmt.utils.asExpr -import kotlinx.collections.immutable.PersistentMap -import kotlinx.collections.immutable.persistentMapOf -import org.usvm.INITIAL_CONCRETE_ADDRESS -import org.usvm.UBoolExpr -import org.usvm.UConcreteHeapAddress -import org.usvm.UConcreteHeapRef -import org.usvm.UContext -import org.usvm.UExpr -import org.usvm.UHeapRef -import org.usvm.USizeExpr -import org.usvm.USort -import org.usvm.sampleUValue - -interface UReadOnlyHeap { - fun readField(ref: Ref, field: Field, sort: Sort): Value - fun readArrayIndex(ref: Ref, index: SizeT, arrayType: ArrayType, sort: Sort): Value - fun readArrayLength(ref: Ref, arrayType: ArrayType): SizeT - - /** - * 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> - -interface UHeap : - UReadOnlyHeap { - fun writeField(ref: Ref, field: Field, sort: Sort, value: Value, guard: Guard) - fun writeArrayIndex( - ref: Ref, - index: SizeT, - type: ArrayType, - sort: Sort, - value: Value, - guard: Guard, - ) - - fun writeArrayLength(ref: Ref, size: SizeT, arrayType: ArrayType) - - fun memset(ref: Ref, type: ArrayType, sort: Sort, contents: Sequence) - fun memcpy( - srcRef: Ref, - dstRef: Ref, - type: ArrayType, - elementSort: Sort, - fromSrcIdx: SizeT, - fromDstIdx: SizeT, - toDstIdx: SizeT, - guard: Guard, - ) - - fun allocate(): UConcreteHeapRef - fun allocateArray(count: SizeT): UConcreteHeapRef - fun allocateArrayInitialized( - type: ArrayType, - sort: Sort, - contents: Sequence - ): UConcreteHeapRef -} - -typealias USymbolicHeap = UHeap, USizeExpr, Field, ArrayType, UBoolExpr> - -/** - * Current heap address holder. Calling [freshAddress] advances counter globally. - * That is, allocation of an object in one state advances counter in all states. - * This would help to avoid overlapping addresses in merged states. - * Copying is prohibited. - */ -class UAddressCounter { - private var lastAddress = INITIAL_CONCRETE_ADDRESS - fun freshAddress(): UConcreteHeapAddress = lastAddress++ -} - -class URegionHeap( - private val ctx: UContext, - private var lastAddress: UAddressCounter = UAddressCounter(), - private var allocatedFields: PersistentMap, UExpr> = persistentMapOf(), - private var inputFields: PersistentMap> = persistentMapOf(), - private var allocatedArrays: PersistentMap> = persistentMapOf(), - private var inputArrays: PersistentMap> = persistentMapOf(), - private var allocatedLengths: PersistentMap = persistentMapOf(), - private var inputLengths: PersistentMap> = persistentMapOf(), -) : USymbolicHeap { - private fun inputFieldRegion( - field: Field, - sort: Sort, - ): UInputFieldRegion = - inputFields[field] - ?.inputFieldsRegionUncheckedCast() - ?: emptyInputFieldRegion(field, sort) - .also { inputFields = inputFields.put(field, it) } // to increase cache usage - - private fun allocatedArrayRegion( - arrayType: ArrayType, - address: UConcreteHeapAddress, - elementSort: Sort, - ): UAllocatedArrayRegion = - allocatedArrays[address] - ?.allocatedArrayRegionUncheckedCast() - ?: emptyAllocatedArrayRegion(arrayType, address, elementSort).also { region -> - allocatedArrays = allocatedArrays.put(address, region) - } // to increase cache usage - - private fun inputArrayRegion( - arrayType: ArrayType, - elementSort: Sort, - ): UInputArrayRegion = - inputArrays[arrayType] - ?.inputArrayRegionUncheckedCast() - ?: emptyInputArrayRegion(arrayType, elementSort).also { region -> - inputArrays = inputArrays.put(arrayType, region) - } // to increase cache usage - - private fun inputArrayLengthRegion( - arrayType: ArrayType, - ): UInputArrayLengthRegion = - inputLengths[arrayType] - ?: emptyInputArrayLengthRegion(arrayType, ctx.sizeSort).also { region -> - inputLengths = inputLengths.put(arrayType, region) - } // to increase cache usage - - override fun readField(ref: UHeapRef, field: Field, sort: Sort): UExpr = - ref.map( - { concreteRef -> - allocatedFields - .getOrDefault(concreteRef.address to field, sort.sampleUValue()) // sampleUValue is important - .asExpr(sort) - }, - { symbolicRef -> inputFieldRegion(field, sort).read(symbolicRef) } - ) - - override fun readArrayIndex( - ref: UHeapRef, - index: USizeExpr, - arrayType: ArrayType, - sort: Sort, - ): UExpr = - ref.map( - { concreteRef -> allocatedArrayRegion(arrayType, concreteRef.address, sort).read(index) }, - { symbolicRef -> inputArrayRegion(arrayType, sort).read(symbolicRef to index) } - ) - - override fun readArrayLength(ref: UHeapRef, arrayType: ArrayType): USizeExpr = - ref.map( - { concreteRef -> allocatedLengths.getOrDefault(concreteRef.address, ctx.sizeSort.sampleUValue()) }, - { symbolicRef -> inputArrayLengthRegion(arrayType).read(symbolicRef) } - ) - - override fun writeField( - ref: UHeapRef, - field: Field, - sort: Sort, - value: UExpr, - guard: UBoolExpr, - ) { - val valueToWrite = value.asExpr(sort) - - withHeapRef( - ref, - guard, - { (concreteRef, innerGuard) -> - val key = concreteRef.address to field - - val oldValue = readField(concreteRef, field, sort) - val newValue = ctx.mkIte(innerGuard, valueToWrite, oldValue) - allocatedFields = allocatedFields.put(key, newValue) - }, - { (symbolicRef, innerGuard) -> - val oldRegion = inputFieldRegion(field, sort) - val newRegion = oldRegion.write(symbolicRef, valueToWrite, innerGuard) - inputFields = inputFields.put(field, newRegion) - - } - ) - } - - override fun writeArrayIndex( - ref: UHeapRef, - index: USizeExpr, - type: ArrayType, - sort: Sort, - value: UExpr, - guard: UBoolExpr, - ) { - val valueToWrite = value.asExpr(sort) - - withHeapRef( - ref, - guard, - { (concreteRef, innerGuard) -> - 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, sort) - val newRegion = oldRegion.write(symbolicRef to index, valueToWrite, innerGuard) - inputArrays = inputArrays.put(type, newRegion) - } - ) - } - - override fun writeArrayLength(ref: UHeapRef, size: USizeExpr, arrayType: ArrayType) { - withHeapRef( - ref, - initialGuard = ctx.trueExpr, - { (concreteRef, guard) -> - val oldSize = readArrayLength(ref, arrayType) - val newSize = ctx.mkIte(guard, size, oldSize) - allocatedLengths = allocatedLengths.put(concreteRef.address, newSize) - }, - { (symbolicRef, guard) -> - val region = inputArrayLengthRegion(arrayType) - val newRegion = region.write(symbolicRef, size, guard) - inputLengths = inputLengths.put(arrayType, newRegion) - } - ) - } - - override fun memset( - ref: UHeapRef, - type: ArrayType, - sort: Sort, - contents: Sequence>, - ) { - val tmpArrayRef = allocateArrayInitialized(type, sort, contents) - val contentLength = allocatedLengths.getValue(tmpArrayRef.address) - memcpy( - srcRef = tmpArrayRef, - dstRef = ref, - type = type, - elementSort = sort, - fromSrcIdx = ctx.mkSizeExpr(0), - fromDstIdx = ctx.mkSizeExpr(0), - toDstIdx = contentLength, - guard = ctx.trueExpr - ) - writeArrayLength(ref, contentLength, type) - } - - override fun memcpy( - srcRef: UHeapRef, - dstRef: UHeapRef, - type: ArrayType, - elementSort: Sort, - fromSrcIdx: USizeExpr, - fromDstIdx: USizeExpr, - toDstIdx: USizeExpr, - guard: UBoolExpr, - ) { - withHeapRef( - srcRef, - guard, - blockOnConcrete = { (srcRef, guard) -> - val srcRegion = allocatedArrayRegion(type, srcRef.address, elementSort) - val src = srcRef to fromSrcIdx - - withHeapRef( - dstRef, - guard, - blockOnConcrete = { (dstRef, deepGuard) -> - val dst = dstRef to fromDstIdx - - val dstRegion = allocatedArrayRegion(type, dstRef.address, elementSort) - val keyConverter = UAllocatedToAllocatedKeyConverter(src, dst, toDstIdx) - val newDstRegion = dstRegion.copyRange(srcRegion, fromDstIdx, toDstIdx, keyConverter, deepGuard) - allocatedArrays = allocatedArrays.put(dstRef.address, newDstRegion) - }, - blockOnSymbolic = { (dstRef, deepGuard) -> - val dst = dstRef to fromDstIdx - - val dstRegion = inputArrayRegion(type, elementSort) - val keyConverter = UAllocatedToInputKeyConverter(src, dst, toDstIdx) - val newDstRegion = dstRegion.copyRange(srcRegion, src, dst, keyConverter, deepGuard) - inputArrays = inputArrays.put(type, newDstRegion) - }, - ) - }, - blockOnSymbolic = { (srcRef, guard) -> - val srcRegion = inputArrayRegion(type, elementSort) - val src = srcRef to fromSrcIdx - - withHeapRef( - dstRef, - guard, - blockOnConcrete = { (dstRef, deepGuard) -> - val dst = dstRef to fromDstIdx - - val dstRegion = allocatedArrayRegion(type, dstRef.address, elementSort) - val keyConverter = UInputToAllocatedKeyConverter(src, dst, toDstIdx) - val newDstRegion = dstRegion.copyRange(srcRegion, fromDstIdx, toDstIdx, keyConverter, deepGuard) - allocatedArrays = allocatedArrays.put(dstRef.address, newDstRegion) - }, - blockOnSymbolic = { (dstRef, deepGuard) -> - val dst = dstRef to fromDstIdx - - val dstRegion = inputArrayRegion(type, elementSort) - val keyConverter = UInputToInputKeyConverter(src, dst, toDstIdx) - val newDstRegion = - dstRegion.copyRange(srcRegion, dst, dstRef to toDstIdx, keyConverter, deepGuard) - inputArrays = inputArrays.put(type, newDstRegion) - }, - ) - }, - ) - } - - override fun allocate() = ctx.mkConcreteHeapRef(lastAddress.freshAddress()) - - override fun allocateArray(count: USizeExpr): UConcreteHeapRef { - val address = lastAddress.freshAddress() - allocatedLengths = allocatedLengths.put(address, count) - return ctx.mkConcreteHeapRef(address) - } - - override fun allocateArrayInitialized( - type: ArrayType, - sort: Sort, - contents: Sequence> - ): UConcreteHeapRef { - val arrayValues = contents.mapTo(mutableListOf()) { it.asExpr(sort) } - val arrayLength = ctx.mkSizeExpr(arrayValues.size) - - val address = allocateArray(arrayLength) - - val initializedArrayRegion = allocateInitializedArrayRegion(type, sort, address.address, arrayValues) - allocatedArrays = allocatedArrays.put(address.address, initializedArrayRegion) - - return address - } - - private fun allocateInitializedArrayRegion( - type: ArrayType, - sort: Sort, - address: UConcreteHeapAddress, - values: List> - ): UAllocatedArrayRegion = initializedAllocatedArrayRegion( - arrayType = type, - address = address, - sort = sort, - content = values.mapIndexed { idx, value -> - ctx.mkSizeExpr(idx) to value - }.toMap(), - guard = ctx.trueExpr - ) - - override fun nullRef(): UHeapRef = ctx.nullRef - - override fun toMutableHeap() = URegionHeap( - ctx, lastAddress, - allocatedFields, inputFields, - allocatedArrays, inputArrays, - allocatedLengths, inputLengths - ) -} - -@Suppress("UNCHECKED_CAST") -fun UInputFieldRegion.inputFieldsRegionUncheckedCast(): UInputFieldRegion = - this as UInputFieldRegion - -@Suppress("UNCHECKED_CAST") -fun UAllocatedArrayRegion.allocatedArrayRegionUncheckedCast(): UAllocatedArrayRegion = - this as UAllocatedArrayRegion - -@Suppress("UNCHECKED_CAST") -fun UInputArrayRegion.inputArrayRegionUncheckedCast(): UInputArrayRegion = - this as UInputArrayRegion diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/HeapRefSplitting.kt b/usvm-core/src/main/kotlin/org/usvm/memory/HeapRefSplitting.kt index 445c50022..3ff32d2a7 100644 --- a/usvm-core/src/main/kotlin/org/usvm/memory/HeapRefSplitting.kt +++ b/usvm-core/src/main/kotlin/org/usvm/memory/HeapRefSplitting.kt @@ -67,43 +67,48 @@ internal fun splitUHeapRef( } /** - * Traverses the [ref], accumulating guards and applying the [blockOnConcrete] on [UConcreteHeapRef]s and - * [blockOnSymbolic] on [USymbolicHeapRef]. An argument for the [blockOnSymbolic] is obtained by removing all concrete - * heap refs from the [ref] if it's ite. + * Accumulates value starting with [initial], traversing [ref], accumulating guards and applying the [blockOnConcrete] + * on [UConcreteHeapRef]s and [blockOnSymbolic] on [USymbolicHeapRef]. An argument for the [blockOnSymbolic] is + * obtained by removing all concrete heap refs from the [ref] if it's ite. * * @param initialGuard the initial value fot the guard to be passed to [blockOnConcrete] and [blockOnSymbolic]. * @param ignoreNullRefs if true, then null references will be ignored. It means that all leafs with nulls * considered unsatisfiable, so we assume their guards equal to false. */ -internal inline fun withHeapRef( +internal inline fun foldHeapRef( ref: UHeapRef, + initial: R, initialGuard: UBoolExpr, - crossinline blockOnConcrete: (GuardedExpr) -> Unit, - crossinline blockOnSymbolic: (GuardedExpr) -> Unit, ignoreNullRefs: Boolean = true, -) { + crossinline blockOnConcrete: (R, GuardedExpr) -> R, + crossinline blockOnSymbolic: (R, GuardedExpr) -> R, +): R { if (initialGuard.isFalse) { - return + return initial } - when (ref) { - is UConcreteHeapRef -> blockOnConcrete(ref with initialGuard) + return when (ref) { + is UConcreteHeapRef -> blockOnConcrete(initial, ref with initialGuard) is UNullRef -> if (!ignoreNullRefs) { - blockOnSymbolic(ref with initialGuard) + blockOnSymbolic(initial, ref with initialGuard) + } else { + initial } - - is USymbolicHeapRef -> blockOnSymbolic(ref with initialGuard) + is USymbolicHeapRef -> blockOnSymbolic(initial, ref with initialGuard) is UIteExpr -> { - val (concreteHeapRefs, symbolicHeapRef) = splitUHeapRef(ref, initialGuard, ignoreNullRefs) + val (concreteHeapRefs, symbolicHeapRef) = splitUHeapRef(ref, initialGuard) - symbolicHeapRef?.let { (ref, guard) -> blockOnSymbolic(ref with guard) } - concreteHeapRefs.onEach { (ref, guard) -> blockOnConcrete(ref with guard) } + var acc = initial + symbolicHeapRef?.let { (ref, guard) -> acc = blockOnSymbolic(acc, ref with guard) } + concreteHeapRefs.onEach { (ref, guard) -> acc = blockOnConcrete(acc, ref with guard) } + acc } else -> error("Unexpected ref: $ref") } } + private const val LEFT_CHILD = 0 private const val RIGHT_CHILD = 1 private const val DONE = 2 diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/Memory.kt b/usvm-core/src/main/kotlin/org/usvm/memory/Memory.kt index 150408ce7..2f1a20e84 100644 --- a/usvm-core/src/main/kotlin/org/usvm/memory/Memory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/memory/Memory.kt @@ -1,166 +1,149 @@ package org.usvm.memory -import io.ksmt.utils.asExpr -import org.usvm.UArrayIndexLValue -import org.usvm.UArrayLengthLValue -import org.usvm.UComposer +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentMapOf +import org.usvm.INITIAL_CONCRETE_ADDRESS +import org.usvm.UBoolExpr +import org.usvm.UConcreteHeapAddress import org.usvm.UConcreteHeapRef import org.usvm.UContext import org.usvm.UExpr -import org.usvm.UFieldLValue import org.usvm.UHeapRef import org.usvm.UIndexedMocker -import org.usvm.ULValue +import org.usvm.UMockEvaluator +import org.usvm.UMockSymbol import org.usvm.UMocker -import org.usvm.URegisterLValue -import org.usvm.USizeExpr import org.usvm.USort import org.usvm.constraints.UTypeConstraints -import org.usvm.types.UTypeStream +import org.usvm.constraints.UTypeEvaluator -interface UReadOnlyMemory { - /** - * Reads value referenced by [lvalue]. Might lazily initialize symbolic values. - */ - fun read(lvalue: LValue): RValue +interface UMemoryRegionId { + val sort: Sort + + fun emptyRegion(): UMemoryRegion } -interface UReadOnlyTypedMemory : UReadOnlyMemory { - /** - * @return a type stream corresponding to the [ref]. - */ - fun typeStreamOf(ref: HeapRef): UTypeStream +interface UReadOnlyMemoryRegion { + fun read(key: Key): UExpr } -interface UMemory : UReadOnlyTypedMemory { - /** - * Writes [rvalue] into memory cell referenced by [lvalue]. - */ - fun write(lvalue: LValue, rvalue: RValue) - - /** - * Allocates dictionary-based structure in heap. - * @return Concrete heap address of an allocated object. - */ - fun alloc(type: Type): HeapRef - - /** - * Allocates array in heap. - * @return Concrete heap address of an allocated array. - */ - fun malloc(arrayType: Type, count: SizeT): HeapRef - - /** - * Allocates array in heap. - * @param contents Sequence of initial array value. - * First element will be written to index 0, second -- to index 1, etc. - * @return Concrete heap address of an allocated array. - */ - fun malloc(arrayType: Type, elementSort: USort, contents: Sequence): HeapRef - - /** - * Optimized writing of many concretely-indexed entries at a time. - * @param contents Sequence of elements to be written. - * First element will be written to index 0, second -- to index 1, etc. - * Updates the length of the array to the length of [contents]. - */ - fun memset(ref: HeapRef, arrayType: Type, elementSort: USort, contents: Sequence) - - /** - * Copies range of elements [[fromSrc]:[fromSrc] + [length] - 1] from an array with address [src] - * to range of elements [[fromDst]:[fromDst] + [length] - 1] of array with address [dst]. - * Both arrays must have type [arrayType]. - */ - fun memcpy( - src: HeapRef, - dst: HeapRef, - arrayType: Type, - elementSort: USort, - fromSrc: SizeT, - fromDst: SizeT, - length: SizeT, - ) +interface UMemoryRegion : UReadOnlyMemoryRegion { + fun write(key: Key, value: UExpr, guard: UBoolExpr): UMemoryRegion } -typealias UReadOnlySymbolicMemory = UReadOnlyTypedMemory, UHeapRef, Type> -typealias USymbolicMemory = UMemory, USizeExpr, UHeapRef, Type> +interface ULValue { + val sort: Sort + val memoryRegionId: UMemoryRegionId + val key: Key +} -@Suppress("MemberVisibilityCanBePrivate") -open class UMemoryBase( - protected val ctx: UContext, - val types: UTypeConstraints, - val stack: URegistersStack = URegistersStack(), - val heap: USymbolicHeap = URegionHeap(ctx), - val mocker: UMocker = UIndexedMocker(ctx), - // TODO: we can eliminate mocker by moving compose to UState? -) : USymbolicMemory { - @Suppress("UNCHECKED_CAST") - override fun read(lvalue: ULValue): UExpr = with(lvalue) { - when (this) { - is URegisterLValue -> stack.readRegister(idx, sort) - is UFieldLValue<*> -> heap.readField(ref, field as Field, sort).asExpr(sort) - is UArrayIndexLValue<*> -> heap.readArrayIndex(ref, index, arrayType as Type, sort).asExpr(sort) - is UArrayLengthLValue<*> -> heap.readArrayLength(ref, arrayType as Type) - else -> error("Unexpected lvalue $this") - } +/** + * Current heap address holder. Calling [freshAddress] advances counter globally. + * That is, allocation of an object in one state advances counter in all states. + * This would help to avoid overlapping addresses in merged states. + * Copying is prohibited. + */ +object UAddressCounter { + private var lastAddress = INITIAL_CONCRETE_ADDRESS + fun freshAddress(): UConcreteHeapAddress = lastAddress++ +} + +interface UReadOnlyMemory { + val stack: UReadOnlyRegistersStack + val mocker: UMockEvaluator + val types: UTypeEvaluator + + private fun read(regionId: UMemoryRegionId, key: Key): UExpr { + val region = getRegion(regionId) + return region.read(key) } + fun read(lvalue: ULValue) = read(lvalue.memoryRegionId, lvalue.key) + + fun getRegion(regionId: UMemoryRegionId): UReadOnlyMemoryRegion + + fun nullRef(): UHeapRef + + fun toWritableMemory(): UWritableMemory +} + +interface UWritableMemory : UReadOnlyMemory { + fun setRegion(regionId: UMemoryRegionId, newRegion: UMemoryRegion) + + fun write(lvalue: ULValue, rvalue: UExpr, guard: UBoolExpr) + + fun alloc(type: Type): UConcreteHeapRef +} + +@Suppress("MemberVisibilityCanBePrivate") +class UMemory( + internal val ctx: UContext, + override val types: UTypeConstraints, + override val stack: URegistersStack = URegistersStack(), + private var mocks: UMocker = UIndexedMocker(ctx), + private var regions: PersistentMap, UMemoryRegion<*, *>> = persistentMapOf(), + internal val addressCounter: UAddressCounter = UAddressCounter, +) : UWritableMemory { + + override val mocker: UMockEvaluator + get() = mocks + @Suppress("UNCHECKED_CAST") - override fun write(lvalue: ULValue, rvalue: UExpr) = with(lvalue) { - when (this) { - is URegisterLValue -> stack.writeRegister(idx, rvalue) - is UFieldLValue<*> -> heap.writeField(ref, field as Field, sort, rvalue, guard = ctx.trueExpr) - is UArrayIndexLValue<*> -> heap.writeArrayIndex( - ref, - index, - arrayType as Type, - sort, - rvalue, - guard = ctx.trueExpr - ) - - is UArrayLengthLValue<*> -> heap.writeArrayLength(ref, rvalue as USizeExpr, arrayType as Type) - else -> error("Unexpected lvalue $this") - } + override fun getRegion(regionId: UMemoryRegionId): UMemoryRegion { + if (regionId is URegisterStackId) return stack as UMemoryRegion + + val region = regions[regionId] + if (region != null) return region as UMemoryRegion + + val newRegion = regionId.emptyRegion() + regions = regions.put(regionId, newRegion) + return newRegion } - override fun alloc(type: Type): UConcreteHeapRef { - val concreteHeapRef = heap.allocate() - types.allocate(concreteHeapRef.address, type) - return concreteHeapRef + override fun setRegion( + regionId: UMemoryRegionId, + newRegion: UMemoryRegion + ) { + if (regionId is URegisterStackId) { + check(newRegion === stack) { "Stack is mutable" } + return + } + regions = regions.put(regionId, newRegion) } - override fun malloc(arrayType: Type, count: USizeExpr): UConcreteHeapRef { - val concreteHeapRef = heap.allocateArray(count) - types.allocate(concreteHeapRef.address, arrayType) - return concreteHeapRef + override fun write(lvalue: ULValue, rvalue: UExpr, guard: UBoolExpr) = + write(lvalue.memoryRegionId, lvalue.key, rvalue, guard) + + private fun write( + regionId: UMemoryRegionId, + key: Key, + value: UExpr, + guard: UBoolExpr + ) { + val region = getRegion(regionId) + val newRegion = region.write(key, value, guard) + setRegion(regionId, newRegion) } - override fun malloc(arrayType: Type, elementSort: USort, contents: Sequence>): UConcreteHeapRef { - val concreteHeapRef = heap.allocateArrayInitialized(arrayType, elementSort, contents) - types.allocate(concreteHeapRef.address, arrayType) + override fun alloc(type: Type): UConcreteHeapRef { + val concreteHeapRef = ctx.mkConcreteHeapRef(addressCounter.freshAddress()) + types.allocate(concreteHeapRef.address, type) return concreteHeapRef } - override fun memset(ref: UHeapRef, arrayType: Type, elementSort: USort, contents: Sequence>) = - heap.memset(ref, arrayType, elementSort, contents) - - override fun memcpy( - src: UHeapRef, dst: UHeapRef, arrayType: Type, elementSort: USort, - fromSrc: USizeExpr, fromDst: USizeExpr, length: USizeExpr, - ) = with(src.ctx) { - val toDst = mkBvAddExpr(fromDst, length) - heap.memcpy(src, dst, arrayType, elementSort, fromSrc, fromDst, toDst, guard = trueExpr) - } + override fun nullRef(): UHeapRef = ctx.nullRef - fun compose(expr: UExpr): UExpr { - val composer = UComposer(ctx, stack, heap, types, mocker) - return composer.compose(expr) + fun mock(body: UMocker.() -> Pair, UMocker>): UMockSymbol { + val (result, updatedMocker) = mocks.body() + mocks = updatedMocker + return result } - fun clone(typeConstraints: UTypeConstraints): UMemoryBase = - UMemoryBase(ctx, typeConstraints, stack.clone(), heap.toMutableHeap(), mocker) + fun clone(typeConstraints: UTypeConstraints): UMemory = + UMemory(ctx, typeConstraints, stack.clone(), mocks, regions, addressCounter) - override fun typeStreamOf(ref: UHeapRef): UTypeStream = - types.getTypeStream(ref) + override fun toWritableMemory() = + // To be perfectly rigorous, we should clone stack and types here. + // But in fact they should not be used, so to optimize things up, we don't touch them. + UMemory(ctx, types, stack, mocks, regions, addressCounter) } diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/MemoryRegions.kt b/usvm-core/src/main/kotlin/org/usvm/memory/MemoryRegions.kt deleted file mode 100644 index 572ad8625..000000000 --- a/usvm-core/src/main/kotlin/org/usvm/memory/MemoryRegions.kt +++ /dev/null @@ -1,447 +0,0 @@ -package org.usvm.memory - -import io.ksmt.utils.asExpr -import kotlinx.collections.immutable.toPersistentMap -import org.usvm.UBoolExpr -import org.usvm.UComposer -import org.usvm.UConcreteHeapAddress -import org.usvm.UConcreteHeapRef -import org.usvm.UConcreteSize -import org.usvm.UExpr -import org.usvm.UHeapRef -import org.usvm.UIndexType -import org.usvm.USizeExpr -import org.usvm.USizeSort -import org.usvm.USort -import org.usvm.sampleUValue -import org.usvm.uctx -import org.usvm.util.ProductRegion -import org.usvm.util.RegionTree -import org.usvm.util.SetRegion -import org.usvm.util.emptyRegionTree - -//region Memory region - - -interface UReadOnlyMemoryRegion { - fun read(key: Key): UExpr -} - - -interface UMemoryRegion : UReadOnlyMemoryRegion { - fun write(key: Key, value: UExpr, guard: UBoolExpr): UMemoryRegion -} - - -/** - * A uniform unbounded slice of memory. Indexed by [Key], stores symbolic values. - * - * @property regionId describes the source of the region. Memory regions with the same [regionId] represent the same - * memory area, but in different states. - * - * @property defaultValue describes the initial values for the region. If [defaultValue] equals `null` then this region - * is filled with symbolics. - */ -data class USymbolicMemoryRegion, Key, Sort : USort>( - val regionId: RegionId, - val updates: UMemoryUpdates, -) : UMemoryRegion { - // to save memory usage - val sort: Sort get() = regionId.sort - - // If we replace it with get(), we have to check for nullability in read function. - private val defaultValue = regionId.defaultValue - - private fun read(key: Key, updates: UMemoryUpdates): UExpr { - val lastUpdatedElement = updates.lastUpdatedElementOrNull() - - if (lastUpdatedElement == null && defaultValue != null) { - // Reading from an untouched array filled with defaultValue - return defaultValue - } - - if (lastUpdatedElement != null) { - if (lastUpdatedElement.includesConcretely(key, precondition = sort.ctx.trueExpr)) { - // The last write has overwritten the key - return lastUpdatedElement.value(key) - } - } - - val localizedRegion = if (updates === this.updates) { - this - } else { - this.copy(updates = updates) - } - - return regionId.instantiate(localizedRegion, key) - } - - 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. - // But doing this for composition seems a little bit strange - return splittingRead(key) { it is UConcreteHeapRef } - } - - val updates = updates.read(key) - return read(key, updates) - } - - /** - * Reads key from this memory region, but 'bubbles up' entries satisfying predicates. - * For example, imagine we read for example key z from array A with two updates: v written into x and w into y. - * Usual [read] produces the expression - * A{x <- v}{y <- w}[z] - * If v satisfies [predicate] and w does not, then [splittingRead] instead produces the expression - * ite(y != z /\ x = z, v, A{y <- w}[z]). - * These two expressions are semantically equivalent, but the second one 'splits' v out of the rest - * memory updates. - */ - private fun splittingRead(key: Key, predicate: (UExpr) -> Boolean): UExpr { - val ctx = sort.ctx - val guardBuilder = GuardBuilder(ctx.trueExpr) - val matchingWrites = ArrayList>>() // works faster than linked list - val splittingUpdates = split(key, predicate, matchingWrites, guardBuilder).updates - - val reading = read(key, splittingUpdates) - - // TODO: maybe introduce special expression for such operations? - val readingWithBubbledWrites = matchingWrites.foldRight(reading) { (expr, guard), acc -> - // foldRight here ^^^^^^^^^ is important - ctx.mkIte(guard, expr, acc) - } - - - return readingWithBubbledWrites - } - - override fun write(key: Key, value: UExpr, guard: UBoolExpr): USymbolicMemoryRegion { - assert(value.sort == sort) - - val newUpdates = if (sort == sort.uctx.addressSort) { - // we must split symbolic and concrete heap refs here, - // because later in [splittingRead] we check value is UConcreteHeapRef - var newUpdates = updates - - withHeapRef( - value.asExpr(sort.uctx.addressSort), - initialGuard = guard, - blockOnConcrete = { (ref, guard) -> newUpdates = newUpdates.write(key, ref.asExpr(sort), guard) }, - blockOnSymbolic = { (ref, guard) -> newUpdates = newUpdates.write(key, ref.asExpr(sort), guard) }, - ignoreNullRefs = false - ) - - newUpdates - } else { - updates.write(key, value, guard) - } - - - return this.copy(updates = newUpdates) - } - - /** - * 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 [USymbolicMemoryRegion] without writes satisfying [predicate] or this [USymbolicMemoryRegion] if no - * matching writes were found. - * @see [UMemoryUpdates.split], [splittingRead] - */ - internal fun split( - key: Key, - predicate: (UExpr) -> Boolean, - matchingWrites: MutableList>>, - guardBuilder: GuardBuilder, - ): USymbolicMemoryRegion { - val splitUpdates = updates.read(key).split(key, predicate, matchingWrites, guardBuilder) - - // we traversed all updates, so predicate says that key misses all the writes to this memory region, - // therefore, the key maps to the default value - if (defaultValue != null && predicate(defaultValue)) { - matchingWrites += defaultValue with guardBuilder.nonMatchingUpdatesGuard - } - - return if (splitUpdates === updates) { - this - } else { - this.copy(updates = splitUpdates) - } - } - - /** - * Maps the region using [composer]. - * It is used in [UComposer] for composition operation. - * - * 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. - */ - fun map( - composer: UComposer, - ): USymbolicMemoryRegion { - // Map the updates and the regionId - val mappedRegionId = regionId.map(composer) - val mappedUpdates = updates.map(regionId.keyMapper(composer), composer) - - if (mappedUpdates === updates && mappedRegionId === regionId) { - return this - } - - return USymbolicMemoryRegion(mappedRegionId, mappedUpdates) - } - - @Suppress("UNCHECKED_CAST") - fun applyTo(heap: USymbolicHeap) { - // Apply each update on the copy - updates.forEach { - when (it) { - is UPinpointUpdateNode -> regionId.write(heap, it.key, it.value, it.guard) - is URangedUpdateNode<*, *, Key, Sort> -> { - it.region.applyTo(heap) - - val (srcFromRef, srcFromIdx) = it.keyConverter.srcSymbolicArrayIndex - val (dstFromRef, dstFromIdx) = it.keyConverter.dstFromSymbolicArrayIndex - val dstToIdx = it.keyConverter.dstToIndex - val arrayType = it.region.regionId.arrayType as Type - - heap.memcpy(srcFromRef, dstFromRef, arrayType, sort, srcFromIdx, dstFromIdx, dstToIdx, it.guard) - } - } - } - } - - /** - * @return Memory region which obtained from this one by overwriting the range of addresses [[fromKey] : [toKey]] - * with values from memory region [fromRegion] read from range - * of addresses [[keyConverter].convert([fromKey]) : [keyConverter].convert([toKey])] - */ - fun , SrcKey> copyRange( - fromRegion: USymbolicMemoryRegion, - fromKey: Key, - toKey: Key, - keyConverter: UMemoryKeyConverter, - guard: UBoolExpr - ): USymbolicMemoryRegion { - val updatesCopy = updates.copyRange(fromRegion, fromKey, toKey, keyConverter, guard) - return this.copy(updates = updatesCopy) - } - - override fun toString(): String = - buildString { - append('<') - if (defaultValue != null) { - append(defaultValue) - } else { - append("_") - } - updates.forEach { - append(it.toString()) - } - append('>') - append('@') - append(regionId) - } -} - -class GuardBuilder(nonMatchingUpdates: UBoolExpr) { - var nonMatchingUpdatesGuard: UBoolExpr = nonMatchingUpdates - private set - - operator fun plusAssign(guard: UBoolExpr) { - nonMatchingUpdatesGuard = guarded(guard) - } - - /** - * @return [expr] guarded by this guard builder. Implementation uses no-flattening operations, because we accumulate - * [nonMatchingUpdatesGuard] and otherwise it would take quadratic time. - */ - fun guarded(expr: UBoolExpr): UBoolExpr = expr.ctx.mkAnd(nonMatchingUpdatesGuard, expr, flat = false) -} - -//endregion - -//region Instantiations - -typealias USymbolicArrayIndex = Pair - -fun heapRefEq(ref1: UHeapRef, ref2: UHeapRef): UBoolExpr = - ref1.uctx.mkHeapRefEq(ref1, ref2) - -@Suppress("UNUSED_PARAMETER") -fun heapRefCmpSymbolic(ref1: UHeapRef, ref2: UHeapRef): UBoolExpr = - error("Heap references should not be compared!") - -@Suppress("UNUSED_PARAMETER") -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) - -fun indexLeSymbolic(idx1: USizeExpr, idx2: USizeExpr): UBoolExpr = - 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) { - return@with (idx1.first eq idx2.first) and indexEq(idx1.second, idx2.second) -} - -fun refIndexCmpSymbolic(idx1: USymbolicArrayIndex, idx2: USymbolicArrayIndex): UBoolExpr = with(idx1.first.ctx) { - return@with (idx1.first eq idx2.first) and indexLeSymbolic(idx1.second, idx2.second) -} - -fun refIndexCmpConcrete(idx1: USymbolicArrayIndex, idx2: USymbolicArrayIndex): Boolean = - idx1.first == idx2.first && indexLeConcrete(idx1.second, idx2.second) - -// TODO: change it to intervals region -typealias UArrayIndexRegion = SetRegion -typealias UInputArrayIndexRegion = ProductRegion, SetRegion> - -fun indexRegion(idx: USizeExpr): UArrayIndexRegion = - when (idx) { - is UConcreteSize -> SetRegion.singleton(idx.numberValue) - else -> SetRegion.universe() - } - -fun refRegion(address: UHeapRef): SetRegion = - when (address) { - is UConcreteHeapRef -> SetRegion.singleton(address) - else -> SetRegion.universe() - } - -fun inputArrayRegion(address: UHeapRef, idx: USizeExpr): UInputArrayIndexRegion = - ProductRegion(refRegion(address), indexRegion(idx)) - -fun indexRangeRegion(idx1: USizeExpr, idx2: USizeExpr): UArrayIndexRegion = - when (idx1) { - is UConcreteSize -> - when (idx2) { - is UConcreteSize -> SetRegion.ofSequence((idx1.numberValue..idx2.numberValue).asSequence()) - else -> SetRegion.universe() - } - - else -> SetRegion.universe() - } - -fun inputArrayRangeRegion( - ref1: UHeapRef, - idx1: USizeExpr, - ref2: UHeapRef, - idx2: USizeExpr, -): ProductRegion, SetRegion> { - val refRegion1 = refRegion(ref1) - val refRegion2 = refRegion(ref2) - require(refRegion1 == refRegion2) - return ProductRegion(refRegion1, indexRangeRegion(idx1, idx2)) -} - -fun refIndexRegion(idx: USymbolicArrayIndex): UInputArrayIndexRegion = inputArrayRegion(idx.first, idx.second) -fun refIndexRangeRegion( - idx1: USymbolicArrayIndex, - idx2: USymbolicArrayIndex, -): UInputArrayIndexRegion = inputArrayRangeRegion(idx1.first, idx1.second, idx2.first, idx2.second) - -typealias UInputFieldRegion = USymbolicMemoryRegion, UHeapRef, Sort> -typealias UAllocatedArrayRegion = USymbolicMemoryRegion, USizeExpr, Sort> -typealias UInputArrayRegion = USymbolicMemoryRegion, USymbolicArrayIndex, Sort> -typealias UInputArrayLengthRegion = USymbolicMemoryRegion, UHeapRef, USizeSort> - -typealias KeyMapper = (Key) -> Key - -val UInputFieldRegion.field - get() = regionId.field - -val UAllocatedArrayRegion.allocatedArrayType - get() = regionId.arrayType -val UAllocatedArrayRegion.allocatedAddress - get() = regionId.address - -val UInputArrayRegion.inputArrayType - get() = regionId.arrayType - -val UInputArrayLengthRegion.inputLengthArrayType - get() = regionId.arrayType - -fun emptyInputFieldRegion( - field: Field, - sort: Sort, -): UInputFieldRegion = - USymbolicMemoryRegion( - UInputFieldId(field, sort, contextHeap = null), - UFlatUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic) - ) - -fun emptyAllocatedArrayRegion( - arrayType: ArrayType, - address: UConcreteHeapAddress, - sort: Sort, -): UAllocatedArrayRegion { - val updates = UTreeUpdates( - updates = emptyRegionTree(), - ::indexRegion, ::indexRangeRegion, ::indexEq, ::indexLeConcrete, ::indexLeSymbolic - ) - return createAllocatedArrayRegion(arrayType, sort, address, updates) -} - -fun initializedAllocatedArrayRegion( - arrayType: ArrayType, - address: UConcreteHeapAddress, - sort: Sort, - content: Map>, - guard: UBoolExpr -): UAllocatedArrayRegion { - val emptyRegionTree = emptyRegionTree>() - - val entries = content.entries.associate { (key, value) -> - val region = indexRegion(key) - val update = UPinpointUpdateNode(key, value, ::indexEq, guard) - region to (update to emptyRegionTree) - } - - val updates = UTreeUpdates( - updates = RegionTree(entries.toPersistentMap()), - ::indexRegion, ::indexRangeRegion, ::indexEq, ::indexLeConcrete, ::indexLeSymbolic - ) - - return createAllocatedArrayRegion(arrayType, sort, address, updates) -} - -private fun createAllocatedArrayRegion( - arrayType: ArrayType, - sort: Sort, - address: UConcreteHeapAddress, - updates: UTreeUpdates -): USymbolicMemoryRegion, USizeExpr, Sort> { - // sampleUValue here is important - val regionId = UAllocatedArrayId(arrayType, sort, sort.sampleUValue(), address, contextHeap = null) - return USymbolicMemoryRegion(regionId, updates) -} - -fun emptyInputArrayRegion( - arrayType: ArrayType, - sort: Sort, -): UInputArrayRegion { - val updates = UTreeUpdates( - updates = emptyRegionTree(), - ::refIndexRegion, ::refIndexRangeRegion, ::refIndexEq, ::refIndexCmpConcrete, ::refIndexCmpSymbolic - ) - return USymbolicMemoryRegion(UInputArrayId(arrayType, sort, contextHeap = null), updates) -} - -fun emptyInputArrayLengthRegion( - arrayType: ArrayType, - sizeSort: USizeSort, -): UInputArrayLengthRegion = - USymbolicMemoryRegion( - UInputArrayLengthId(arrayType, sizeSort, contextHeap = null), - UFlatUpdates(::heapRefEq, ::heapRefCmpConcrete, ::heapRefCmpSymbolic), - ) - -//endregion diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/RegionIds.kt b/usvm-core/src/main/kotlin/org/usvm/memory/RegionIds.kt deleted file mode 100644 index 1a76ce165..000000000 --- a/usvm-core/src/main/kotlin/org/usvm/memory/RegionIds.kt +++ /dev/null @@ -1,248 +0,0 @@ -package org.usvm.memory - -import io.ksmt.utils.asExpr -import org.usvm.UBoolExpr -import org.usvm.UComposer -import org.usvm.UConcreteHeapAddress -import org.usvm.UExpr -import org.usvm.UExprTransformer -import org.usvm.UHeapRef -import org.usvm.USizeExpr -import org.usvm.USizeSort -import org.usvm.USort -import org.usvm.isTrue -import org.usvm.uctx - -/** - * Represents any possible type of regions that can be used in the memory. - */ -interface URegionId> { - val sort: Sort - - val defaultValue: 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, - key: Key, - value: UExpr, - guard: UBoolExpr, - ) - - fun keyMapper(transformer: UExprTransformer): KeyMapper - - fun map(composer: UComposer): RegionId -} - -/** - * A region id for a region storing the specific [field]. - */ -data class UInputFieldId internal constructor( - val field: Field, - 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, key) - } else { - region.applyTo(contextHeap) - contextHeap.readField(key, field, sort).asExpr(sort) - } - - @Suppress("UNCHECKED_CAST") - override fun write( - heap: USymbolicHeap, - key: UHeapRef, - value: UExpr, - guard: UBoolExpr, - ) = heap.writeField(key, field as Field, sort, value, guard) - - override fun keyMapper( - transformer: UExprTransformer, - ): KeyMapper = { transformer.apply(it) } - - 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 toString(): String { - return "inputField($field)" - } -} - -interface UArrayId> : - URegionId { - val arrayType: ArrayType -} - -/** - * 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( - override val arrayType: ArrayType, - 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 - ): UExpr = if (contextHeap == null) { - sort.uctx.mkAllocatedArrayReading(region, key) - } else { - region.applyTo(contextHeap) - val ref = key.uctx.mkConcreteHeapRef(address) - contextHeap.readArrayIndex(ref, key, arrayType, sort).asExpr(sort) - } - - @Suppress("UNCHECKED_CAST") - override fun write( - heap: USymbolicHeap, - key: USizeExpr, - value: UExpr, - guard: UBoolExpr, - ) { - val ref = key.uctx.mkConcreteHeapRef(address) - 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) - 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 - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UAllocatedArrayId<*, *> - - if (address != other.address) return false - - return true - } - - override fun hashCode(): Int { - return address - } - - override fun toString(): String { - return "allocatedArray($address)" - } -} - -/** - * A region id for a region storing arrays retrieved as a symbolic value, contains only its [arrayType]. - */ -data class UInputArrayId internal constructor( - override val arrayType: ArrayType, - override val sort: Sort, - 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) - contextHeap.readArrayIndex(key.first, key.second, arrayType, sort).asExpr(sort) - } - - @Suppress("UNCHECKED_CAST") - 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( - transformer: UExprTransformer, - ): KeyMapper = { - 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 { - 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)" - } -} - -/** - * A region id for a region storing array lengths for arrays of a specific [arrayType]. - */ -data class UInputArrayLengthId internal constructor( - val arrayType: ArrayType, - override val sort: USizeSort, - 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) - contextHeap.readArrayLength(key, arrayType) - } - - @Suppress("UNCHECKED_CAST") - override fun write( - heap: USymbolicHeap, - key: UHeapRef, - value: UExpr, - guard: UBoolExpr, - ) { - assert(guard.isTrue) - heap.writeArrayLength(key, value.asExpr(key.uctx.sizeSort), arrayType as ArrayType) - } - - override fun keyMapper( - transformer: UExprTransformer, - ): KeyMapper = { transformer.apply(it) } - - 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 toString(): String { - return "length($arrayType)" - } -} diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/RegistersStack.kt b/usvm-core/src/main/kotlin/org/usvm/memory/RegistersStack.kt index ab97d257c..dfe221d55 100644 --- a/usvm-core/src/main/kotlin/org/usvm/memory/RegistersStack.kt +++ b/usvm-core/src/main/kotlin/org/usvm/memory/RegistersStack.kt @@ -2,12 +2,27 @@ package org.usvm.memory import io.ksmt.expr.KExpr import io.ksmt.utils.asExpr +import org.usvm.UBoolExpr import org.usvm.UExpr import org.usvm.USort +import org.usvm.isTrue import org.usvm.uctx -interface URegistersStackEvaluator { - fun readRegister(registerIndex: Int, sort: Sort): UExpr +object URegisterStackId : UMemoryRegionId, USort> { + override val sort: USort + get() = error("Register stack has not sort") + + override fun emptyRegion(): UMemoryRegion, USort> = URegistersStack() +} + +class URegisterStackLValue( + override val sort: Sort, + val idx: Int +) : ULValue, USort> { + override val memoryRegionId: UMemoryRegionId, USort> + get() = URegisterStackId + + override val key: URegisterStackLValue = this } class URegistersStackFrame( @@ -25,9 +40,15 @@ class URegistersStackFrame( fun clone() = URegistersStackFrame(registers.clone()) } +interface UReadOnlyRegistersStack: UReadOnlyMemoryRegion, USort> { + fun readRegister(index: Int, sort: Sort): KExpr + + override fun read(key: URegisterStackLValue<*>): UExpr = readRegister(key.idx, key.sort) +} + class URegistersStack( private val stack: MutableList = mutableListOf(), -) : URegistersStackEvaluator { +) : UReadOnlyRegistersStack, UMemoryRegion, USort> { fun push(registersCount: Int) = stack.add(URegistersStackFrame(registersCount)) fun push(argumentsCount: Int, localsCount: Int) = @@ -36,8 +57,18 @@ class URegistersStack( fun push(arguments: Array>, localsCount: Int) = stack.add(URegistersStackFrame(arguments, localsCount)) - override fun readRegister(registerIndex: Int, sort: Sort): KExpr = - stack.last()[registerIndex]?.asExpr(sort) ?: sort.uctx.mkRegisterReading(registerIndex, sort) + override fun readRegister(index: Int, sort: Sort): KExpr = + stack.lastOrNull()?.get(index)?.asExpr(sort) ?: sort.uctx.mkRegisterReading(index, sort) + + override fun write( + key: URegisterStackLValue<*>, + value: UExpr, + guard: UBoolExpr + ): UMemoryRegion, USort> { + check(guard.isTrue) { "Guarded writes are not supported for register" } + writeRegister(key.idx, value) + return this + } fun writeRegister(index: Int, value: UExpr) { stack.last()[index] = value diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/MemoryUpdates.kt b/usvm-core/src/main/kotlin/org/usvm/memory/SymbolicCollectionUpdates.kt similarity index 73% rename from usvm-core/src/main/kotlin/org/usvm/memory/MemoryUpdates.kt rename to usvm-core/src/main/kotlin/org/usvm/memory/SymbolicCollectionUpdates.kt index 88f2459af..81adafad9 100644 --- a/usvm-core/src/main/kotlin/org/usvm/memory/MemoryUpdates.kt +++ b/usvm-core/src/main/kotlin/org/usvm/memory/SymbolicCollectionUpdates.kt @@ -12,27 +12,31 @@ import org.usvm.util.emptyRegionTree /** * Represents a sequence of memory writes. */ -interface UMemoryUpdates : Sequence> { +interface USymbolicCollectionUpdates : Sequence> { /** * @return Relevant updates for a given key. */ - fun read(key: Key): UMemoryUpdates + fun read(key: Key): USymbolicCollectionUpdates /** - * @return Memory region which is obtained from this one by overwriting the address [key] with value [value] + * @return Symbolic collection which is obtained from this one by overwriting the address [key] with value [value] * guarded with condition [guard]. */ - fun write(key: Key, value: UExpr, guard: UBoolExpr = value.ctx.trueExpr): UMemoryUpdates + fun write( + key: Key, + value: UExpr, + guard: UBoolExpr = value.ctx.trueExpr + ): USymbolicCollectionUpdates /** - * Splits this [UMemoryUpdates] into two parts: + * Splits this [USymbolicCollectionUpdates] into two parts: * * Values of [UUpdateNode]s satisfying [predicate] are added to the [matchingWrites]. * * [UUpdateNode]s unsatisfying [predicate] remain in the result updates. * * The [guardBuilder] is used to build guards for values added to [matchingWrites]. In the end, the [guardBuilder] * is updated and contains a predicate indicating that the [key] can't be included in any of visited [UUpdateNode]s. * - * @return new [UMemoryUpdates] without writes satisfying [predicate]. + * @return new [USymbolicCollectionUpdates] without writes satisfying [predicate]. * @see [UUpdateNode.split] */ fun split( @@ -40,27 +44,30 @@ interface UMemoryUpdates : Sequence> { predicate: (UExpr) -> Boolean, matchingWrites: MutableList>>, guardBuilder: GuardBuilder, - ): UMemoryUpdates + ): USymbolicCollectionUpdates /** - * Returns a mapped [USymbolicMemoryRegion] using [keyMapper] and [composer]. + * Returns a mapped [USymbolicCollection] using [keyMapper] and [composer]. * It is used in [UComposer] during memory composition. + * Throws away all updates for which [keyMapper] returns null. */ - fun map(keyMapper: KeyMapper, composer: UComposer): UMemoryUpdates + fun > filterMap( + keyMapper: KeyMapper, + composer: UComposer, + mappedKeyInfo: USymbolicCollectionKeyInfo + ): USymbolicCollectionUpdates /** - * @return Updates which express copying the slice of [fromRegion] guarded with + * @return Updates which express copying the slice of [fromCollection] guarded with * condition [guard]. * - * @see USymbolicMemoryRegion.copyRange + * @see USymbolicCollection.copyRange */ - fun , SrcKey> copyRange( - fromRegion: USymbolicMemoryRegion, - fromKey: Key, - toKey: Key, - keyConverter: UMemoryKeyConverter, + fun , SrcKey> copyRange( + fromCollection: USymbolicCollection, + adapter: USymbolicCollectionAdapter, guard: UBoolExpr, - ): UMemoryUpdates + ): USymbolicCollectionUpdates /** * Returns the last updated element if there were any updates or null otherwise. @@ -81,7 +88,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. A caller is responsible to maintain the lifetime of the [lookupCache]. + * [USymbolicCollectionUpdates] implementation. A caller is responsible to maintain the lifetime of the [lookupCache]. * * @return the final result. */ @@ -107,15 +114,9 @@ interface UMemoryUpdatesVisitor { class UFlatUpdates private constructor( internal val node: UFlatUpdatesNode?, - private val symbolicEq: (Key, Key) -> UBoolExpr, - private val concreteCmp: (Key, Key) -> Boolean, - private val symbolicCmp: (Key, Key) -> UBoolExpr, -) : UMemoryUpdates { - constructor( - symbolicEq: (Key, Key) -> UBoolExpr, - concreteCmp: (Key, Key) -> Boolean, - symbolicCmp: (Key, Key) -> UBoolExpr, - ) : this(node = null, symbolicEq, concreteCmp, symbolicCmp) + private val keyInfo: USymbolicCollectionKeyInfo, +) : USymbolicCollectionUpdates { + constructor(keyInfo: USymbolicCollectionKeyInfo) : this(node = null, keyInfo) internal data class UFlatUpdatesNode( val update: UUpdateNode, @@ -128,28 +129,32 @@ class UFlatUpdates private constructor( else -> this } - override fun write(key: Key, value: UExpr, guard: UBoolExpr): UFlatUpdates = + override fun write( + key: Key, + value: UExpr, + guard: UBoolExpr + ): UFlatUpdates = UFlatUpdates( - UFlatUpdatesNode(UPinpointUpdateNode(key, value, symbolicEq, guard), this), - symbolicEq, - concreteCmp, - symbolicCmp + UFlatUpdatesNode( + UPinpointUpdateNode( + key, + keyInfo, + value, + guard + ), this + ), keyInfo ) - override fun , SrcKey> copyRange( - fromRegion: USymbolicMemoryRegion, - fromKey: Key, - toKey: Key, - keyConverter: UMemoryKeyConverter, + override fun , SrcKey> copyRange( + fromCollection: USymbolicCollection, + adapter: USymbolicCollectionAdapter, guard: UBoolExpr, - ): UMemoryUpdates = UFlatUpdates( + ): USymbolicCollectionUpdates = UFlatUpdates( UFlatUpdatesNode( - URangedUpdateNode(fromKey, toKey, fromRegion, concreteCmp, symbolicCmp, keyConverter, guard), + URangedUpdateNode(fromCollection, adapter, guard), this ), - symbolicEq, - concreteCmp, - symbolicCmp + keyInfo ) override fun split( @@ -170,17 +175,30 @@ class UFlatUpdates private constructor( return this } - return UFlatUpdates(UFlatUpdatesNode(splitNode, splitNext), symbolicEq, concreteCmp, symbolicCmp) + return UFlatUpdates( + UFlatUpdatesNode(splitNode, splitNext), + keyInfo + ) } - override fun map( - keyMapper: KeyMapper, - composer: UComposer, - ): UFlatUpdates { - node ?: return this + override fun > filterMap( + keyMapper: KeyMapper, + composer: UComposer, + mappedKeyInfo: USymbolicCollectionKeyInfo + ): UFlatUpdates { + node ?: return if (keyInfo == mappedKeyInfo) { + @Suppress("UNCHECKED_CAST") + this as UFlatUpdates + } else { + UFlatUpdates(null, mappedKeyInfo) + } + // 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, mappedKeyInfo) + val mappedNext = node.next.filterMap(keyMapper, composer, mappedKeyInfo) + if (mappedNode == null) { + return mappedNext + } // Doesn't apply the node, if its guard maps to `false` if (mappedNode.guard.isFalse) { @@ -188,19 +206,27 @@ class UFlatUpdates private constructor( } // If nothing changed, return this updates - if (mappedNode === node.update && mappedNext === node.next) { - return this + if (mappedNode === node.update && mappedNext === node.next && keyInfo == mappedKeyInfo) { + // In this case Key = MappedKey is guaranteed, but type system can't express this + @Suppress("UNCHECKED_CAST") + return (this as UFlatUpdates) } // Otherwise, construct a new one using the mapped values - return UFlatUpdates(UFlatUpdatesNode(mappedNode, mappedNext), symbolicEq, concreteCmp, symbolicCmp) + return UFlatUpdates( + UFlatUpdatesNode( + mappedNode, + mappedNext + ), mappedKeyInfo + ) } /** * Returns updates in the FIFO order: the iterator emits updates from the oldest updates to the most recent one. * It means that the `initialNode` from the [UFlatUpdatesIterator] will be returned as the last element. */ - override fun iterator(): Iterator> = UFlatUpdatesIterator(initialNode = this) + override fun iterator(): Iterator> = + UFlatUpdatesIterator(initialNode = this) private class UFlatUpdatesIterator( initialNode: UFlatUpdates, @@ -255,14 +281,10 @@ class UFlatUpdates private constructor( data class UTreeUpdates, Sort : USort>( private val updates: RegionTree>, - private val keyToRegion: (Key) -> Reg, - private val keyRangeToRegion: (Key, Key) -> Reg, - private val symbolicEq: (Key, Key) -> UBoolExpr, - private val concreteCmp: (Key, Key) -> Boolean, - private val symbolicCmp: (Key, Key) -> UBoolExpr, -) : UMemoryUpdates { + private val keyInfo: USymbolicCollectionKeyInfo +) : USymbolicCollectionUpdates { override fun read(key: Key): UTreeUpdates { - val reg = keyToRegion(key) + val reg = keyInfo.keyToRegion(key) val updates = updates.localize(reg) { it.includesSymbolically(key).isFalse } if (updates === this.updates) { return this @@ -271,10 +293,14 @@ data class UTreeUpdates, Sort : USort>( return this.copy(updates = updates) } - override fun write(key: Key, value: UExpr, guard: UBoolExpr): UTreeUpdates { - val update = UPinpointUpdateNode(key, value, symbolicEq, guard) + override fun write( + key: Key, + value: UExpr, + guard: UBoolExpr + ): UTreeUpdates { + val update = UPinpointUpdateNode(key, keyInfo, value, guard) val newUpdates = updates.write( - keyToRegion(key), + keyInfo.keyToRegion(key), update, valueFilter = { it.isIncludedByUpdateConcretely(update) } ) @@ -282,17 +308,14 @@ data class UTreeUpdates, Sort : USort>( return this.copy(updates = newUpdates) } - override fun , SrcKey> copyRange( - fromRegion: USymbolicMemoryRegion, - fromKey: Key, - toKey: Key, - keyConverter: UMemoryKeyConverter, - guard: UBoolExpr, + override fun , SrcKey> copyRange( + fromCollection: USymbolicCollection, + adapter: USymbolicCollectionAdapter, + guard: UBoolExpr ): UTreeUpdates { - val region = keyRangeToRegion(fromKey, toKey) - val update = URangedUpdateNode(fromKey, toKey, fromRegion, concreteCmp, symbolicCmp, keyConverter, guard) + val update = URangedUpdateNode(fromCollection, adapter, guard) val newUpdates = updates.write( - region, + adapter.region(), update, valueFilter = { it.isIncludedByUpdateConcretely(update) } ) @@ -312,8 +335,8 @@ data class UTreeUpdates, Sort : USort>( // add an update to result tree fun applyUpdate(update: UUpdateNode) { val region = when (update) { - is UPinpointUpdateNode -> keyToRegion(update.key) - is URangedUpdateNode<*, *, Key, Sort> -> keyRangeToRegion(update.fromKey, update.toKey) + is UPinpointUpdateNode -> keyInfo.keyToRegion(update.key) + is URangedUpdateNode<*, *, Key, Sort> -> update.adapter.region() } splitRegionTree = splitRegionTree.write(region, update, valueFilter = { it.isIncludedByUpdateConcretely(update) }) @@ -347,18 +370,19 @@ data class UTreeUpdates, Sort : USort>( } - override fun map( - keyMapper: KeyMapper, - composer: UComposer, - ): UTreeUpdates { + override fun > filterMap( + keyMapper: KeyMapper, + composer: UComposer, + mappedKeyInfo: USymbolicCollectionKeyInfo + ): UTreeUpdates { var mappedNodeFound = false // Traverse [updates] using its iterator and fold them into a new updates tree with new mapped nodes - val initialEmptyTree = emptyRegionTree>() + val initialEmptyTree = emptyRegionTree>() val mappedUpdates = updates.fold(initialEmptyTree) { mappedUpdatesTree, updateNodeWithRegion -> - val (updateNode, oldRegion) = updateNodeWithRegion + val (updateNode, _) = updateNodeWithRegion // Map current node - val mappedUpdateNode = updateNode.map(keyMapper, composer) + val mappedUpdateNode = updateNode.map(keyMapper, composer, mappedKeyInfo) // Save information about whether something changed in the current node or not @@ -366,8 +390,8 @@ data class UTreeUpdates, Sort : USort>( mappedNodeFound = true } - // Doesn't apply the node, if its guard maps to `false` - if (mappedUpdateNode.guard.isFalse) { + // Ignore nodes which don't go into [targetCollectionId] after mapping + if (mappedUpdateNode == null) { return@fold mappedUpdatesTree } @@ -378,13 +402,12 @@ data class UTreeUpdates, Sort : USort>( // Extract a new region by the mapped node val newRegion = when (mappedUpdateNode) { is UPinpointUpdateNode -> { - val currentRegion = keyToRegion(mappedUpdateNode.key) - oldRegion.intersect(currentRegion) + mappedKeyInfo.keyToRegion(mappedUpdateNode.key) } - is URangedUpdateNode<*, *, Key, Sort> -> { - val currentRegion = keyRangeToRegion(mappedUpdateNode.fromKey, mappedUpdateNode.toKey) - oldRegion.intersect(currentRegion) + is URangedUpdateNode<*, *, *, Sort> -> { + mappedUpdateNode as URangedUpdateNode<*, *, MappedKey, Sort> + mappedUpdateNode.adapter.region() } } @@ -399,7 +422,12 @@ data class UTreeUpdates, Sort : USort>( } // If at least one node was changed, return a new updates, otherwise return this - return if (mappedNodeFound) copy(updates = mappedUpdates) else this + return if (mappedNodeFound || mappedKeyInfo != keyInfo) { + UTreeUpdates(updates = mappedUpdates, mappedKeyInfo) + } else { + @Suppress("UNCHECKED_CAST") + this as UTreeUpdates + } } /** @@ -463,8 +491,8 @@ data class UTreeUpdates, Sort : USort>( // 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) + is UPinpointUpdateNode -> keyInfo.keyToRegion(update.key) + is URangedUpdateNode<*, *, Key, Sort> -> update.adapter.region() } val wasCloned = initialRegion != region return wasCloned diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/USymbolicCollection.kt b/usvm-core/src/main/kotlin/org/usvm/memory/USymbolicCollection.kt new file mode 100644 index 000000000..d5ba56f7a --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/memory/USymbolicCollection.kt @@ -0,0 +1,233 @@ +package org.usvm.memory + +import io.ksmt.utils.asExpr +import kotlinx.collections.immutable.PersistentMap +import org.usvm.UBoolExpr +import org.usvm.UComposer +import org.usvm.UConcreteHeapRef +import org.usvm.UExpr +import org.usvm.USort +import org.usvm.uctx +import kotlin.collections.ArrayList + +/** + * A uniform unbounded slice of memory. Indexed by [Key], stores symbolic values. + * + * @property collectionId describes the source of the collection. Symbolic collections with the same [collectionId] represent the same + * memory area, but in different states. + */ +data class USymbolicCollection, Key, Sort : USort>( + val collectionId: CollectionId, + val updates: USymbolicCollectionUpdates, +) : UMemoryRegion { + // to save memory usage + val sort: Sort get() = collectionId.sort + + private fun read( + key: Key, + updates: USymbolicCollectionUpdates + ): UExpr { + val lastUpdatedElement = updates.lastUpdatedElementOrNull() + + if (lastUpdatedElement != null) { + if (lastUpdatedElement.includesConcretely(key, precondition = sort.ctx.trueExpr)) { + // The last write has overwritten the key + return lastUpdatedElement.value(key) + } + } + + val localizedRegion = if (updates === this.updates) { + this + } else { + this.copy(updates = updates) + } + + return collectionId.instantiate(localizedRegion, key) + } + + 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 } + } + + val updates = updates.read(key) + return read(key, updates) + } + + /** + * Reads key from this symbolic collection, but 'bubbles up' entries satisfying predicates. + * For example, imagine we read for example key z from array A with two updates: v written into x and w into y. + * Usual [read] produces the expression + * A{x <- v}{y <- w}[z] + * If v satisfies [predicate] and w does not, then [splittingRead] instead produces the expression + * ite(y != z /\ x = z, v, A{y <- w}[z]). + * These two expressions are semantically equivalent, but the second one 'splits' v out of the rest + * memory updates. + */ + private fun splittingRead( + key: Key, + predicate: (UExpr) -> Boolean + ): UExpr { + val ctx = sort.ctx + val guardBuilder = GuardBuilder(ctx.trueExpr) + val matchingWrites = ArrayList>>() // works faster than linked list + val splittingUpdates = split(key, predicate, matchingWrites, guardBuilder).updates + + val reading = read(key, splittingUpdates) + + // TODO: maybe introduce special expression for such operations? + val readingWithBubbledWrites = matchingWrites.foldRight(reading) { (expr, guard), acc -> + // foldRight here ^^^^^^^^^ is important + ctx.mkIte(guard, expr, acc) + } + + + return readingWithBubbledWrites + } + + override fun write( + key: Key, + value: UExpr, + guard: UBoolExpr + ): USymbolicCollection { + assert(value.sort == sort) + + val newUpdates = if (sort == sort.uctx.addressSort) { + // we must split symbolic and concrete heap refs here, + // because later in [splittingRead] we check value is UConcreteHeapRef + foldHeapRef( + ref = value.asExpr(value.uctx.addressSort), + initial = updates, + initialGuard = guard, + ignoreNullRefs = false, + blockOnConcrete = { newUpdates, (valueRef, valueGuard) -> + newUpdates.write(key, valueRef.asExpr(sort), valueGuard) + }, + blockOnSymbolic = { newUpdates, (valueRef, valueGuard) -> + newUpdates.write(key, valueRef.asExpr(sort), valueGuard) + } + ) + } else { + updates.write(key, value, guard) + } + + + return this.copy(updates = newUpdates) + } + + /** + * Splits this [USymbolicCollection] on two parts: + * * Values of [UUpdateNode]s satisfying [predicate] are added to the [matchingWrites]. + * * [UUpdateNode]s unsatisfying [predicate] remain in the result sumbolic collection. + * + * 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 [USymbolicCollection] without writes satisfying [predicate] or this [USymbolicCollection] if no + * matching writes were found. + * @see [USymbolicCollectionUpdates.split], [splittingRead] + */ + internal fun split( + key: Key, + predicate: (UExpr) -> Boolean, + matchingWrites: MutableList>>, + guardBuilder: GuardBuilder, + ): USymbolicCollection { + val splitUpdates = updates.read(key).split(key, predicate, matchingWrites, guardBuilder) + + return if (splitUpdates === updates) { + this + } else { + this.copy(updates = splitUpdates) + } + } + + /** + * Maps the collection using [composer]. + * It is used in [UComposer] for composition operation. + * All updates which after mapping do not touch [targetCollectionId] will be thrown out. + * + * Note: after this operation a collection returned as a result might be in `broken` state: + * it might [UIteExpr] with both symbolic and concrete references as keys in it. + */ + fun mapTo( + composer: UComposer, + targetCollectionId: USymbolicCollectionId + ): USymbolicCollection, MappedKey, Sort> { + val mapper = collectionId.keyFilterMapper(composer, targetCollectionId) + // Map the updates and the collectionId + val mappedUpdates = updates.filterMap(mapper, composer, targetCollectionId.keyInfo()) + + if (mappedUpdates === updates && targetCollectionId === collectionId) { + @Suppress("UNCHECKED_CAST") + return this as USymbolicCollection, MappedKey, Sort> + } + + return USymbolicCollection(targetCollectionId, mappedUpdates) + } + + fun applyTo(memory: UWritableMemory) { + // Apply each update on the copy + updates.forEach { + when (it) { + is UPinpointUpdateNode -> collectionId.write(memory, it.key, it.value, it.guard) + is URangedUpdateNode<*, *, Key, Sort> -> it.applyTo(memory, collectionId) + } + } + } + + /** + * @return Symbolic collection which obtained from this one by overwriting the range of addresses [[fromKey] : [toKey]] + * with values from collection [fromCollection] read from range + * of addresses [[keyConverter].convert([fromKey]) : [keyConverter].convert([toKey])] + */ + fun , SrcKey> copyRange( + fromCollection: USymbolicCollection, + adapter: USymbolicCollectionAdapter, + guard: UBoolExpr + ): USymbolicCollection { + val updatesCopy = updates.copyRange(fromCollection, adapter, guard) + return this.copy(updates = updatesCopy) + } + + override fun toString(): String = + buildString { + append('<') + updates.forEach { + append(it.toString()) + } + append('>') + append('@') + append(collectionId) + } +} + +class GuardBuilder(nonMatchingUpdates: UBoolExpr) { + var nonMatchingUpdatesGuard: UBoolExpr = nonMatchingUpdates + private set + + operator fun plusAssign(guard: UBoolExpr) { + nonMatchingUpdatesGuard = guarded(guard) + } + + /** + * @return [expr] guarded by this guard builder. Implementation uses [UContext.mkAnd] without flattening, because we accumulate + * [nonMatchingUpdatesGuard] and otherwise it would take quadratic time. + */ + fun guarded(expr: UBoolExpr): UBoolExpr = expr.ctx.mkAnd(nonMatchingUpdatesGuard, expr, flat = false) +} + +inline fun PersistentMap>.guardedWrite( + key: K, + value: UExpr, + guard: UBoolExpr, + defaultValue: () -> UExpr +): PersistentMap> { + val guardedValue = guard.uctx.mkIte( + guard, + { value }, + { get(key) ?: defaultValue() } + ) + return put(key, guardedValue) +} diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/USymbolicCollectionAdapter.kt b/usvm-core/src/main/kotlin/org/usvm/memory/USymbolicCollectionAdapter.kt new file mode 100644 index 000000000..1aa21fe7d --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/memory/USymbolicCollectionAdapter.kt @@ -0,0 +1,85 @@ +package org.usvm.memory + +import org.usvm.UBoolExpr +import org.usvm.UComposer +import org.usvm.USort +import org.usvm.util.Region + +/** + * Redirects reads from one collection into another. Used in [URangedUpdateNode]. + */ +interface USymbolicCollectionAdapter { + /** + * Converts destination memory key into source memory key + */ + fun convert(key: DstKey): SrcKey + + /** + * Key that defines adapted collection id (used to find id after mapping). + */ + val srcKey: SrcKey + + /** + * Returns region covered by the adapted collection. + */ + fun > region(): Reg + + fun includesConcretely(key: DstKey): Boolean + + fun includesSymbolically(key: DstKey): UBoolExpr + + fun isIncludedByUpdateConcretely( + update: UUpdateNode, + guard: UBoolExpr, + ): Boolean + + /** + * Maps this adapter by substituting all symbolic values using composer. + * The type of adapter might change in both [SrcKey] and [DstKey]. + * Type of [SrcKey] changes if [collectionId] rebinds [srcKey]. + * Type of [DstKey] changes to [MappedDstKey] by [dstKeyMapper]. + * @return + * - Null if destination keys are filtered out by [dstKeyMapper] + * - Pair(adapter, targetId), where adapter is a mapped version of this one, targetId is a + * new collection id for the mapped source collection we adapt. + */ + fun map( + dstKeyMapper: KeyMapper, + composer: UComposer, + collectionId: USymbolicCollectionId, + mappedKeyInfo: USymbolicCollectionKeyInfo + ): Pair, USymbolicCollectionId<*, Sort, *>>? { + val mappedSrcKey = collectionId.keyMapper(composer)(srcKey) + val decomposedSrcKey = collectionId.rebindKey(mappedSrcKey) + if (decomposedSrcKey != null) { + val mappedAdapter = + mapDstKeys(decomposedSrcKey.key, decomposedSrcKey.collectionId, dstKeyMapper, composer, mappedKeyInfo) + ?: return null + return mappedAdapter to decomposedSrcKey.collectionId + } + + val mappedAdapter = mapDstKeys(mappedSrcKey, collectionId, dstKeyMapper, composer, mappedKeyInfo) ?: return null + return mappedAdapter to collectionId + } + + /** + * Returns new adapter with destination keys were successfully mapped by [dstKeyMapper]. + * If [dstKeyMapper] returns null for at least one key, returns null. + */ + fun mapDstKeys( + mappedSrcKey: MappedSrcKey, + srcCollectionId: USymbolicCollectionId<*, *, *>, + dstKeyMapper: KeyMapper, + composer: UComposer, + mappedKeyInfo: USymbolicCollectionKeyInfo + ): USymbolicCollectionAdapter? + + fun applyTo( + memory: UWritableMemory, + srcCollectionId: USymbolicCollectionId, + dstCollectionId: USymbolicCollectionId, + guard: UBoolExpr + ) + + fun toString(collection: USymbolicCollection<*, SrcKey, *>): String +} diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/USymbolicCollectionId.kt b/usvm-core/src/main/kotlin/org/usvm/memory/USymbolicCollectionId.kt new file mode 100644 index 000000000..9410c6f4b --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/memory/USymbolicCollectionId.kt @@ -0,0 +1,127 @@ +package org.usvm.memory + +import org.usvm.UBoolExpr +import org.usvm.UComposer +import org.usvm.UContext +import org.usvm.UExpr +import org.usvm.USort +import org.usvm.UTransformer +import org.usvm.uctx + +typealias KeyTransformer = (Key) -> Key +typealias KeyMapper = (Key) -> MappedKey? + +data class DecomposedKey(val collectionId: USymbolicCollectionId, val key: Key) + +/** + * Represents any possible type of symbolic collections that can be used in symbolic memory. + */ +interface USymbolicCollectionId> { + val sort: Sort + + /** + * Performs a reading from a [collection] by a [key]. Inheritors use context heap in symbolic collection composition. + */ + fun instantiate(collection: USymbolicCollection<@UnsafeVariance CollectionId, Key, Sort>, key: Key): UExpr + + fun write( + memory: UWritableMemory, + key: Key, + value: UExpr, + guard: UBoolExpr, + ) + + /** + * Maps keys that belong to this collection using [transformer]. + * */ + fun keyMapper(transformer: UTransformer): KeyTransformer + + /** + * Maps keys that belong to this collection to the collection with [expectedId] + * using [transformer]. + * Filters out keys that don't belong to the collection with [expectedId] after mapping. + * */ + fun keyFilterMapper( + transformer: UTransformer, + expectedId: USymbolicCollectionId + ): KeyMapper { + val mapper = keyMapper(transformer) + return filter@{ currentKey -> + val transformedKey = mapper(currentKey) + val decomposedKey = rebindKey(transformedKey) + + @Suppress("UNCHECKED_CAST") + return@filter when { + // transformedKey belongs to the symbolic collection with expectedId. + decomposedKey == null -> transformedKey + + /** + * Transformed key has been rebound to the collection with expectedId. + * For example, the expectedId is UAllocatedFieldId with address 0x1 + * and transformedKey has been rebound to the collection with the same id. + * */ + decomposedKey.collectionId == expectedId -> decomposedKey.key + + /** + * Transformed key has been rebound to the collection with id different from expectedId. + * For example, the expectedId is UAllocatedFieldId with address 0x1 + * and transformedKey has been rebound to the UAllocatedFieldId with address 0x2. + * Therefore, the key definitely doesn't belong to the + * collection with expectedId and can be filtered out. + * */ + else -> null + } as MappedKey? + } + } + + /** + * Maps the collection using [composer]. + * It is used in [UComposer] for composition operation. + */ + fun map(composer: UComposer): CollectionId + + /** + * Checks that [key] still belongs to the symbolic collection with this id. If yes, then returns null. + * If [key] belongs to some new memory region, returns lvalue for this new region. + * The implementation might assume that [key] is obtained by [keyMapper] from some key of symbolic collection with this id. + */ + fun rebindKey(key: Key): DecomposedKey<*, Sort>? + + /** + * Returns information about the key of this collection. + * TODO: pass here context in the form of path constraints here. + */ + fun keyInfo(): USymbolicCollectionKeyInfo + + fun emptyRegion(): USymbolicCollection +} + +abstract class USymbolicCollectionIdWithContextMemory< + Key, Sort : USort, + out CollectionId : USymbolicCollectionId>( + val contextMemory: UWritableMemory<*>?, +) : USymbolicCollectionId { + + override fun instantiate( + collection: USymbolicCollection<@UnsafeVariance CollectionId, Key, Sort>, + key: Key + ): UExpr = if (contextMemory == null) { + sort.uctx.mkReading(collection, key) + } else { + collection.applyTo(contextMemory) + val lValue = sort.uctx.mkLValue(key) + contextMemory.read(lValue) + } + + abstract fun UContext.mkReading( + collection: USymbolicCollection<@UnsafeVariance CollectionId, Key, Sort>, + key: Key + ): UExpr + + abstract fun UContext.mkLValue(key: Key): ULValue<*, Sort> + + override fun write(memory: UWritableMemory, key: Key, value: UExpr, guard: UBoolExpr) { + val lValue = guard.uctx.mkLValue(key) + memory.write(lValue, value, guard) + } +} diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/USymbolicCollectionKeyInfo.kt b/usvm-core/src/main/kotlin/org/usvm/memory/USymbolicCollectionKeyInfo.kt new file mode 100644 index 000000000..0831c9b07 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/memory/USymbolicCollectionKeyInfo.kt @@ -0,0 +1,53 @@ +package org.usvm.memory + +import org.usvm.UBoolExpr +import org.usvm.UContext +import org.usvm.util.Region + +/** + * Provides information about entities used as keys of symbolic collections. + */ +interface USymbolicCollectionKeyInfo> { + /** + * Returns symbolic expression guaranteeing that [key1] is same as [key2]. + */ + fun eqSymbolic(ctx: UContext, key1: Key, key2: Key): UBoolExpr + + /** + * Returns if [key1] is same as [key2] in all possible models. + */ + fun eqConcrete(key1: Key, key2: Key): Boolean + + /** + * Returns symbolic expression guaranteeing that [key1] is less or equal to [key2]. + * Assumes that [Key] domain is linearly ordered. + */ + fun cmpSymbolicLe(ctx: UContext, key1: Key, key2: Key): UBoolExpr + + /** + * Returns if [key1] is less or equal to [key2] in all possible models. + * Assumes that [Key] domain is linearly ordered. + */ + fun cmpConcreteLe(key1: Key, key2: Key): Boolean + + /** + * Returns region that over-approximates the possible values of [key]. + */ + fun keyToRegion(key: Key): Reg + + /** + * Returns region that over-approximates the range of indices [[from] .. [to]] + */ + fun keyRangeRegion(from: Key, to: Key): Reg + + /** + * Returns region that represents any possible key. + */ + + fun topRegion(): Reg + + /** + * Returns region that represents empty set of keys. + */ + fun bottomRegion(): Reg +} diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/UpdateNodes.kt b/usvm-core/src/main/kotlin/org/usvm/memory/UpdateNodes.kt index 2281bc5b0..621f4807f 100644 --- a/usvm-core/src/main/kotlin/org/usvm/memory/UpdateNodes.kt +++ b/usvm-core/src/main/kotlin/org/usvm/memory/UpdateNodes.kt @@ -3,10 +3,9 @@ package org.usvm.memory import org.usvm.UBoolExpr import org.usvm.UComposer import org.usvm.UExpr -import org.usvm.USizeExpr import org.usvm.USort import org.usvm.isTrue -import java.util.* +import org.usvm.uctx /** * Represents the result of memory write operation. @@ -65,11 +64,13 @@ sealed interface UUpdateNode { /** * Returns a mapped update node using [keyMapper] and [composer]. * It is used in [UComposer] for composition. + * For some key, [keyMapper] might return null. Then, this function returns null as well. */ - fun map( - keyMapper: KeyMapper, - composer: UComposer - ): UUpdateNode + fun map( + keyMapper: KeyMapper, + composer: UComposer, + mappedKeyInfo: USymbolicCollectionKeyInfo + ): UUpdateNode? } /** @@ -77,8 +78,8 @@ sealed interface UUpdateNode { */ class UPinpointUpdateNode( val key: Key, + val keyInfo: USymbolicCollectionKeyInfo, internal val value: UExpr, - private val keyEqualityComparer: (Key, Key) -> UBoolExpr, override val guard: UBoolExpr, ) : UUpdateNode { override fun includesConcretely(key: Key, precondition: UBoolExpr) = @@ -86,9 +87,11 @@ class UPinpointUpdateNode( // in fact, we can check less strict formulae: `precondition -> guard`, but it is too complex to compute. override fun includesSymbolically(key: Key): UBoolExpr = - guard.ctx.mkAnd(keyEqualityComparer(this.key, key), guard) + guard.ctx.mkAnd(keyInfo.eqSymbolic(guard.uctx, 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 @@ -116,151 +119,62 @@ class UPinpointUpdateNode( return res } - override fun map( - keyMapper: KeyMapper, - composer: UComposer - ): UPinpointUpdateNode { - val mappedKey = keyMapper(key) + override fun map( + keyMapper: KeyMapper, + composer: UComposer, + mappedKeyInfo: USymbolicCollectionKeyInfo + ): UPinpointUpdateNode? { + val mappedKey = keyMapper(key) ?: return null val mappedValue = composer.compose(value) val mappedGuard = composer.compose(guard) // If nothing changed, return this value if (mappedKey === key && mappedValue === value && mappedGuard === guard) { - return this + @Suppress("UNCHECKED_CAST") + return this as UPinpointUpdateNode } // Otherwise, construct a new one update node - return UPinpointUpdateNode(mappedKey, mappedValue, keyEqualityComparer, mappedGuard) + return UPinpointUpdateNode(mappedKey, mappedKeyInfo, mappedValue, mappedGuard) } override fun equals(other: Any?): Boolean = - other is UPinpointUpdateNode<*, *> && this.key == other.key && this.guard == other.guard + other is UPinpointUpdateNode<*, *> && this.key == other.key && this.guard == other.guard override fun hashCode(): Int = key.hashCode() * 31 + guard.hashCode() // Ignores value override fun toString(): String = "{$key <- $value}".takeIf { guard.isTrue } ?: "{$key <- $value | $guard}" } -/** - * Composable converter of memory region keys. Helps to transparently copy content of various regions - * each into other without eager address conversion. - * For instance, when we copy array slice [i : i + len] to destination memory slice [j : j + len], - * we emulate it by memorizing the source memory updates as-is, but read the destination memory by - * 'redirecting' the index k to k + j - i of the source memory. - * This conversion is done by [convert]. - * Do not be confused: it converts [DstKey] to [SrcKey] (not vice-versa), as we use it when we - * read from destination buffer index to source memory. - */ -sealed class UMemoryKeyConverter( - val srcSymbolicArrayIndex: USymbolicArrayIndex, - val dstFromSymbolicArrayIndex: USymbolicArrayIndex, - val dstToIndex: USizeExpr -) { - /** - * Converts source memory key into destination memory key - */ - abstract fun convert(key: DstKey): SrcKey - - protected fun convertIndex(idx: USizeExpr): USizeExpr = with(srcSymbolicArrayIndex.first.ctx) { - mkBvSubExpr(mkBvAddExpr(idx, dstFromSymbolicArrayIndex.second), srcSymbolicArrayIndex.second) - } - - abstract fun clone( - srcSymbolicArrayIndex: USymbolicArrayIndex, - dstFromSymbolicArrayIndex: USymbolicArrayIndex, - dstToIndex: USizeExpr - ): UMemoryKeyConverter - - fun map(composer: UComposer): UMemoryKeyConverter { - val (srcRef, srcIdx) = srcSymbolicArrayIndex - val (dstRef, dstIdx) = dstFromSymbolicArrayIndex - - val newSrcHeapAddr = composer.compose(srcRef) - val newSrcArrayIndex = composer.compose(srcIdx) - val newDstHeapAddress = composer.compose(dstRef) - val newDstFromIndex = composer.compose(dstIdx) - val newDstToIndex = composer.compose(dstToIndex) - - if (newSrcHeapAddr === srcRef && - newSrcArrayIndex === srcIdx && - newDstHeapAddress === dstRef && - newDstFromIndex === dstIdx && - newDstToIndex === dstToIndex - ) { - return this - } - - return clone( - srcSymbolicArrayIndex = newSrcHeapAddr to newSrcArrayIndex, - dstFromSymbolicArrayIndex = newDstHeapAddress to newDstFromIndex, - dstToIndex = newDstToIndex - ) - } -} - /** * Represents a synchronous overwriting the range of addresses [[fromKey] : [toKey]] - * with values from memory region [region] read from range - * of addresses [[keyConverter].convert([fromKey]) : [keyConverter].convert([toKey])] + * with values from symbolic collection [sourceCollection] read from range + * of addresses [[adapter].convert([fromKey]) : [adapter].convert([toKey])] */ -class URangedUpdateNode, SrcKey, DstKey, Sort : USort>( - val fromKey: DstKey, - val toKey: DstKey, - val region: USymbolicMemoryRegion, - private val concreteComparer: (DstKey, DstKey) -> Boolean, - private val symbolicComparer: (DstKey, DstKey) -> UBoolExpr, - val keyConverter: UMemoryKeyConverter, - override val guard: UBoolExpr +class URangedUpdateNode, SrcKey, DstKey, Sort : USort>( + val sourceCollection: USymbolicCollection, + val adapter: USymbolicCollectionAdapter, + override val guard: UBoolExpr, ) : 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? - // in fact, we can check less strict formulae: precondition _implies_ guard, but this is too complex to compute. - override fun includesSymbolically(key: DstKey): UBoolExpr { - val leftIsLefter = symbolicComparer(fromKey, key) - val rightIsRighter = symbolicComparer(key, toKey) - val ctx = leftIsLefter.ctx - - return ctx.mkAnd(leftIsLefter, rightIsRighter, guard) - } + override fun includesConcretely( + key: DstKey, + precondition: UBoolExpr, + ): Boolean = + adapter.includesConcretely(key) && + (guard == guard.ctx.trueExpr || precondition == guard) // TODO: some optimizations here? + // in fact, we can check less strict formulae: precondition _implies_ guard, but this is too complex to compute. - 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 includesSymbolically(key: DstKey): UBoolExpr = + guard.ctx.mkAnd(adapter.includesSymbolically(key), guard) - override fun map( - keyMapper: KeyMapper, - composer: UComposer - ): URangedUpdateNode { - val mappedFromKey = keyMapper(fromKey) - val mappedToKey = keyMapper(toKey) - val mappedRegion = region.map(composer) - val mappedKeyConverter = keyConverter.map(composer) - val mappedGuard = composer.compose(guard) + override fun isIncludedByUpdateConcretely( + update: UUpdateNode, + ): Boolean = + adapter.isIncludedByUpdateConcretely(update, guard) - // If nothing changed, return this - if (mappedFromKey === fromKey - && mappedToKey === toKey - && mappedRegion === region - && mappedKeyConverter === keyConverter - && mappedGuard === guard - ) { - return this - } - - // Otherwise, construct a new one updated node - return URangedUpdateNode( - mappedFromKey, - mappedToKey, - mappedRegion, - concreteComparer, - symbolicComparer, - mappedKeyConverter, - mappedGuard - ) - } + override fun value(key: DstKey): UExpr = sourceCollection.read(adapter.convert(key)) override fun split( key: DstKey, @@ -275,7 +189,7 @@ class URangedUpdateNode, SrcKey, val nextGuardBuilder = GuardBuilder(nextGuard) /** - * Here's the explanation of the [split] function. Consider these memory regions and memory updates: + * Here's the explanation of the [split] function. Consider these symbolic collections and updates: * * ``` * [this] [UPinpointUpdateNode] @@ -295,15 +209,16 @@ class URangedUpdateNode, SrcKey, * Also, the result [matchingWrites] must contain { 0x1 }, but this must be guarded: [key] !in { 1..5 } which * is implied from [nodeExcludesKey]. * - * Due to the [GuardBuilder] mutability, we have to create a new guard to pass into the [region.split] function, + * Due to the [GuardBuilder] mutability, we have to create a new guard to pass into the [sourceCollection.split] function, * it's a [nextGuardBuilder]. */ - val splitRegion = region.split(keyConverter.convert(key), predicate, matchingWrites, nextGuardBuilder) + val splitCollection = + sourceCollection.split(adapter.convert(key), predicate, matchingWrites, nextGuardBuilder) // ??? - val resultUpdateNode = if (splitRegion === region) { + val resultUpdateNode = if (splitCollection === sourceCollection) { this } else { - URangedUpdateNode(fromKey, toKey, splitRegion, concreteComparer, symbolicComparer, keyConverter, guard) + URangedUpdateNode(splitCollection, adapter, guard) } guardBuilder += nodeExcludesKey @@ -311,91 +226,51 @@ class URangedUpdateNode, SrcKey, return resultUpdateNode } + + @Suppress("UNCHECKED_CAST") + override fun map( + keyMapper: KeyMapper, + composer: UComposer, + mappedKeyInfo: USymbolicCollectionKeyInfo + ): URangedUpdateNode<*, *, MappedDstKey, Sort>? { + val mappedCollectionId = sourceCollection.collectionId.map(composer) + val (mappedAdapter, targetCollectionId) = adapter.map(keyMapper, composer, mappedCollectionId, mappedKeyInfo) + ?: return null + val mappedGuard = composer.compose(guard) + + val mappedCollection = sourceCollection.mapTo(composer, targetCollectionId) + + // If nothing changed, return this + if (mappedCollection === sourceCollection + && mappedAdapter === adapter + && mappedGuard === guard + ) { + return this as URangedUpdateNode<*, *, MappedDstKey, Sort> + } + + // Otherwise, construct a new one updated node + return URangedUpdateNode( + // Type variables in this cast are incorrect, but who cares... + mappedCollection as USymbolicCollection, + mappedAdapter as USymbolicCollectionAdapter, + mappedGuard + ) + } + // Ignores update override fun equals(other: Any?): Boolean = other is URangedUpdateNode<*, *, *, *> && - this.fromKey == other.fromKey && - this.toKey == other.toKey && + this.adapter == other.adapter && this.guard == other.guard // Ignores update - override fun hashCode(): Int = (17 * fromKey.hashCode() + toKey.hashCode()) * 31 + guard.hashCode() + override fun hashCode(): Int = adapter.hashCode() * 31 + guard.hashCode() - override fun toString(): String { - return "{[$fromKey..$toKey] <- $region[keyConv($fromKey)..keyConv($toKey)]" + - ("}".takeIf { guard.isTrue } ?: " | $guard}") + fun applyTo(memory: UWritableMemory<*>, dstCollectionId: USymbolicCollectionId) { + sourceCollection.applyTo(memory) + adapter.applyTo(memory, sourceCollection.collectionId, dstCollectionId, guard) } -} - -/** - * Used when copying data from allocated array to another allocated array. - */ -class UAllocatedToAllocatedKeyConverter( - srcSymbolicArrayIndex: USymbolicArrayIndex, - dstFromSymbolicArrayIndex: USymbolicArrayIndex, - dstToIndex: USizeExpr -) : UMemoryKeyConverter(srcSymbolicArrayIndex, dstFromSymbolicArrayIndex, dstToIndex) { - override fun convert(key: USizeExpr): USizeExpr = convertIndex(key) - - override fun clone( - srcSymbolicArrayIndex: USymbolicArrayIndex, - dstFromSymbolicArrayIndex: USymbolicArrayIndex, - dstToIndex: USizeExpr - ) = UAllocatedToAllocatedKeyConverter(srcSymbolicArrayIndex, dstFromSymbolicArrayIndex, dstToIndex) -} -/** - * Used when copying data from allocated array to input one. - */ -class UAllocatedToInputKeyConverter( - srcSymbolicArrayIndex: USymbolicArrayIndex, - dstFromSymbolicArrayIndex: USymbolicArrayIndex, - dstToIndex: USizeExpr -) : UMemoryKeyConverter(srcSymbolicArrayIndex, dstFromSymbolicArrayIndex, dstToIndex) { - override fun convert(key: USymbolicArrayIndex): USizeExpr = convertIndex(key.second) - - override fun clone( - srcSymbolicArrayIndex: USymbolicArrayIndex, - dstFromSymbolicArrayIndex: USymbolicArrayIndex, - dstToIndex: USizeExpr - ) = UAllocatedToInputKeyConverter(srcSymbolicArrayIndex, dstFromSymbolicArrayIndex, dstToIndex) -} - -/** - * Used when copying data from input array to allocated one. - */ -class UInputToAllocatedKeyConverter( - srcSymbolicArrayIndex: USymbolicArrayIndex, - dstFromSymbolicArrayIndex: USymbolicArrayIndex, - dstToIndex: USizeExpr -) : UMemoryKeyConverter(srcSymbolicArrayIndex, dstFromSymbolicArrayIndex, dstToIndex) { - override fun convert(key: USizeExpr): USymbolicArrayIndex = srcSymbolicArrayIndex.first to convertIndex(key) - - override fun clone( - srcSymbolicArrayIndex: USymbolicArrayIndex, - dstFromSymbolicArrayIndex: USymbolicArrayIndex, - dstToIndex: USizeExpr - ) = UInputToAllocatedKeyConverter(srcSymbolicArrayIndex, dstFromSymbolicArrayIndex, dstToIndex) -} - -/** - * Used when copying data from input array to another input array. - */ -class UInputToInputKeyConverter( - srcFromSymbolicArrayIndex: USymbolicArrayIndex, - dstFromSymbolicArrayIndex: USymbolicArrayIndex, - dstToIndex: USizeExpr -) : UMemoryKeyConverter( - srcFromSymbolicArrayIndex, - dstFromSymbolicArrayIndex, - dstToIndex -) { - override fun convert(key: USymbolicArrayIndex): USymbolicArrayIndex = - srcSymbolicArrayIndex.first to convertIndex(key.second) - - override fun clone( - srcSymbolicArrayIndex: USymbolicArrayIndex, - dstFromSymbolicArrayIndex: USymbolicArrayIndex, - dstToIndex: USizeExpr - ) = UInputToInputKeyConverter(srcSymbolicArrayIndex, dstFromSymbolicArrayIndex, dstToIndex) + override fun toString(): String = + "{${adapter.toString(sourceCollection)}${if (guard.isTrue) "" else " | $guard"}}" } diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/key/UHeapRefKeyInfo.kt b/usvm-core/src/main/kotlin/org/usvm/memory/key/UHeapRefKeyInfo.kt new file mode 100644 index 000000000..9dd73f976 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/memory/key/UHeapRefKeyInfo.kt @@ -0,0 +1,44 @@ +package org.usvm.memory.key + +import org.usvm.UBoolExpr +import org.usvm.UConcreteHeapAddress +import org.usvm.UConcreteHeapRef +import org.usvm.UContext +import org.usvm.UHeapRef +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.util.SetRegion + +typealias UHeapRefRegion = SetRegion + +/** + * Provides information about heap references used as symbolic collection keys. + */ +object UHeapRefKeyInfo : USymbolicCollectionKeyInfo { + override fun eqSymbolic(ctx: UContext, key1: UHeapRef, key2: UHeapRef): UBoolExpr = + ctx.mkHeapRefEq(key1, key2) + + override fun eqConcrete(key1: UHeapRef, key2: UHeapRef): Boolean = + key1 == key2 + + override fun cmpSymbolicLe(ctx: UContext, key1: UHeapRef, key2: UHeapRef): UBoolExpr = + error("Heap references should not be compared!") + + override fun cmpConcreteLe(key1: UHeapRef, key2: UHeapRef): Boolean = + error("Heap references should not be compared!") + + override fun keyToRegion(key: UHeapRef) = + if (key is UConcreteHeapRef) { + SetRegion.singleton(key.address) + } else { + SetRegion.universe() + } + + override fun keyRangeRegion(from: UHeapRef, to: UHeapRef) = + error("This should not be called!") + + override fun topRegion() = + SetRegion.universe() + + override fun bottomRegion() = + SetRegion.empty() +} diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/key/USingleKeyInfo.kt b/usvm-core/src/main/kotlin/org/usvm/memory/key/USingleKeyInfo.kt new file mode 100644 index 000000000..717b6cf34 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/memory/key/USingleKeyInfo.kt @@ -0,0 +1,20 @@ +package org.usvm.memory.key + +import org.usvm.UBoolExpr +import org.usvm.UContext +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.util.TrivialRegion + +object USingleKeyInfo : USymbolicCollectionKeyInfo { + override fun eqSymbolic(ctx: UContext, key1: Unit, key2: Unit): UBoolExpr = ctx.trueExpr + override fun eqConcrete(key1: Unit, key2: Unit): Boolean = true + override fun cmpSymbolicLe(ctx: UContext, key1: Unit, key2: Unit): UBoolExpr = singleKeyError() + override fun cmpConcreteLe(key1: Unit, key2: Unit): Boolean = singleKeyError() + override fun keyToRegion(key: Unit): TrivialRegion = singleKeyError() + override fun keyRangeRegion(from: Unit, to: Unit): TrivialRegion = singleKeyError() + override fun topRegion(): TrivialRegion = singleKeyError() + override fun bottomRegion(): TrivialRegion = singleKeyError() + + private fun singleKeyError(): Nothing = + error("Unexpected operation on single key") +} \ No newline at end of file diff --git a/usvm-core/src/main/kotlin/org/usvm/memory/key/USizeExprKeyInfo.kt b/usvm-core/src/main/kotlin/org/usvm/memory/key/USizeExprKeyInfo.kt new file mode 100644 index 000000000..6dc990e20 --- /dev/null +++ b/usvm-core/src/main/kotlin/org/usvm/memory/key/USizeExprKeyInfo.kt @@ -0,0 +1,52 @@ +package org.usvm.memory.key + +import org.usvm.UBoolExpr +import org.usvm.UConcreteSize +import org.usvm.UContext +import org.usvm.USizeExpr +import org.usvm.USizeType +import org.usvm.memory.USymbolicCollectionKeyInfo +import org.usvm.util.SetRegion + +// TODO: change it to intervals region +typealias USizeRegion = SetRegion + +/** + * Provides information about numeric values used as symbolic collection keys. + */ +object USizeExprKeyInfo : USymbolicCollectionKeyInfo { + override fun eqSymbolic(ctx: UContext, key1: USizeExpr, key2: USizeExpr): UBoolExpr = + ctx.mkEq(key1, key2) + + override fun eqConcrete(key1: USizeExpr, key2: USizeExpr): Boolean = + key1 === key2 + + override fun cmpSymbolicLe(ctx: UContext, key1: USizeExpr, key2: USizeExpr): UBoolExpr = + ctx.mkBvSignedLessOrEqualExpr(key1, key2) + + override fun cmpConcreteLe(key1: USizeExpr, key2: USizeExpr): Boolean = + key1 == key2 || (key1 is UConcreteSize && key2 is UConcreteSize && key1.numberValue <= key2.numberValue) + + override fun keyToRegion(key: USizeExpr) = + when (key) { + is UConcreteSize -> SetRegion.singleton(key.numberValue) + else -> SetRegion.universe() + } + + override fun keyRangeRegion(from: USizeExpr, to: USizeExpr) = + when (from) { + is UConcreteSize -> + when (to) { + is UConcreteSize -> SetRegion.ofSequence((from.numberValue..to.numberValue).asSequence()) + else -> SetRegion.universe() + } + + else -> SetRegion.universe() + } + + override fun topRegion() = + SetRegion.universe() + + override fun bottomRegion() = + SetRegion.empty() +} diff --git a/usvm-core/src/main/kotlin/org/usvm/model/EagerModels.kt b/usvm-core/src/main/kotlin/org/usvm/model/EagerModels.kt index 6a170d5a8..8b729f45b 100644 --- a/usvm-core/src/main/kotlin/org/usvm/model/EagerModels.kt +++ b/usvm-core/src/main/kotlin/org/usvm/model/EagerModels.kt @@ -2,22 +2,13 @@ package org.usvm.model import io.ksmt.utils.asExpr import io.ksmt.utils.sampleValue -import org.usvm.INITIAL_INPUT_ADDRESS -import org.usvm.UBoolExpr -import org.usvm.UComposer import org.usvm.UConcreteHeapRef import org.usvm.UExpr -import org.usvm.UHeapRef import org.usvm.UIndexedMethodReturnValue import org.usvm.UMockEvaluator import org.usvm.UMockSymbol -import org.usvm.USizeExpr -import org.usvm.USizeSort import org.usvm.USort -import org.usvm.memory.UReadOnlyMemoryRegion -import org.usvm.memory.URegistersStackEvaluator -import org.usvm.memory.USymbolicArrayIndex -import org.usvm.memory.USymbolicHeap +import org.usvm.memory.UReadOnlyRegistersStack import org.usvm.uctx /** @@ -27,12 +18,12 @@ import org.usvm.uctx class URegistersStackEagerModel( private val nullRef: UConcreteHeapRef, private val registers: Map> -) : URegistersStackEvaluator { +) : UReadOnlyRegistersStack { override fun readRegister( - registerIndex: Int, + index: Int, sort: Sort, ): UExpr = registers - .getOrElse(registerIndex) { sort.sampleValue().nullAddress(nullRef) } // sampleValue here is important + .getOrElse(index) { sort.sampleValue().nullAddress(nullRef) } // sampleValue here is important .asExpr(sort) } @@ -58,125 +49,6 @@ class UIndexedMockEagerModel( } } -/** - * 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 happened, 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 <= INITIAL_INPUT_ADDRESS) - - @Suppress("UNCHECKED_CAST") - val region = resolvedInputFields.getOrElse(field) { - // sampleValue here is important - UMemory1DArray(sort.sampleValue().nullAddress(nullRef)) - } as UReadOnlyMemoryRegion - - 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) { - // sampleValue here is important - UMemory2DArray(sort.sampleValue().nullAddress(nullRef)) - } as UReadOnlyMemoryRegion - - 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) { - // sampleValue here is important - 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 allocateArrayInitialized( - type: ArrayType, - sort: Sort, - contents: Sequence> - ) = 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) diff --git a/usvm-core/src/main/kotlin/org/usvm/model/LazyModelDecoder.kt b/usvm-core/src/main/kotlin/org/usvm/model/LazyModelDecoder.kt index 673d1b88f..daa22dd55 100644 --- a/usvm-core/src/main/kotlin/org/usvm/model/LazyModelDecoder.kt +++ b/usvm-core/src/main/kotlin/org/usvm/model/LazyModelDecoder.kt @@ -9,10 +9,10 @@ import org.usvm.UAddressSort import org.usvm.UConcreteHeapRef import org.usvm.UContext import org.usvm.UExpr -import org.usvm.memory.UMemoryBase import org.usvm.solver.UExprTranslator +import org.usvm.UMockEvaluator -interface UModelDecoder { +interface UModelDecoder { fun decode(model: KModel): Model } @@ -20,11 +20,11 @@ interface UModelDecoder { * Initializes [UExprTranslator] and [UModelDecoder] and returns them. We can safely reuse them while [UContext] is * alive. */ -fun buildTranslatorAndLazyDecoder( +fun buildTranslatorAndLazyDecoder( ctx: UContext, -): Pair, ULazyModelDecoder> { - val translator = UExprTranslator(ctx) - val decoder = ULazyModelDecoder(translator) +): Pair, ULazyModelDecoder> { + val translator = UExprTranslator(ctx) + val decoder = ULazyModelDecoder(translator) return translator to decoder } @@ -37,9 +37,9 @@ typealias AddressesMapping = Map, UConcreteHeapRef> * * @param translator an expression translator used for encoding constraints. */ -open class ULazyModelDecoder( - protected val translator: UExprTranslator, -) : UModelDecoder, UModelBase> { +open class ULazyModelDecoder( + protected val translator: UExprTranslator, +) : UModelDecoder> { private val ctx: UContext = translator.ctx private val translatedNullRef = translator.translate(ctx.nullRef) @@ -77,15 +77,29 @@ open class ULazyModelDecoder( */ override fun decode( model: KModel, - ): UModelBase { + ): UModelBase { val addressesMapping = buildMapping(model) val stack = decodeStack(model, addressesMapping) - val heap = decodeHeap(model, addressesMapping) + val regions = decodeHeap(model, addressesMapping) val types = UTypeModel(ctx.typeSystem(), typeStreamByAddr = emptyMap()) val mocks = decodeMocker(model, addressesMapping) - return UModelBase(ctx, stack, heap, types, mocks) + /** + * To resolve nullRef, we need to: + * * translate it + * * evaluate the translated value in the [model] + * * map the evaluated value with the [addressesMapping] + * + * Actually, its address should always be equal 0. + */ + val nullRef = model + .eval(translator.translate(translator.ctx.nullRef)) + .mapAddress(addressesMapping) as UConcreteHeapRef + + check(nullRef.address == NULL_ADDRESS) { "Incorrect null ref: $nullRef" } + + return UModelBase(ctx, stack, types, mocks, regions, nullRef) } private fun decodeStack( @@ -103,16 +117,14 @@ open class ULazyModelDecoder( private fun decodeHeap( model: KModel, addressesMapping: AddressesMapping, - ): ULazyHeapModel = ULazyHeapModel( - model, - addressesMapping, - translator, - ) + ) = translator.regionIdToDecoder.mapValues { (_, decoder) -> + decoder.decodeLazyRegion(model, addressesMapping) + } private fun decodeMocker( model: KModel, addressesMapping: AddressesMapping, - ): ULazyIndexedMockModel = ULazyIndexedMockModel( + ): UMockEvaluator = ULazyIndexedMockModel( model, addressesMapping, translator diff --git a/usvm-core/src/main/kotlin/org/usvm/model/LazyModels.kt b/usvm-core/src/main/kotlin/org/usvm/model/LazyModels.kt index ec5d8dbbe..378cf41da 100644 --- a/usvm-core/src/main/kotlin/org/usvm/model/LazyModels.kt +++ b/usvm-core/src/main/kotlin/org/usvm/model/LazyModels.kt @@ -2,30 +2,12 @@ package org.usvm.model import io.ksmt.solver.KModel import io.ksmt.utils.asExpr -import io.ksmt.utils.cast -import org.usvm.INITIAL_INPUT_ADDRESS -import org.usvm.NULL_ADDRESS -import org.usvm.UBoolExpr -import org.usvm.UComposer -import org.usvm.UConcreteHeapRef import org.usvm.UExpr -import org.usvm.UHeapReading -import org.usvm.UHeapRef import org.usvm.UIndexedMethodReturnValue import org.usvm.UMockEvaluator import org.usvm.UMockSymbol -import org.usvm.USizeExpr -import org.usvm.USizeSort import org.usvm.USort -import org.usvm.memory.UInputArrayId -import org.usvm.memory.UInputArrayLengthId -import org.usvm.memory.UInputFieldId -import org.usvm.memory.UMemoryRegion -import org.usvm.memory.UReadOnlyMemoryRegion -import org.usvm.memory.URegionId -import org.usvm.memory.URegistersStackEvaluator -import org.usvm.memory.USymbolicArrayIndex -import org.usvm.memory.USymbolicHeap +import org.usvm.memory.UReadOnlyRegistersStack import org.usvm.solver.UExprTranslator import org.usvm.uctx @@ -40,15 +22,15 @@ import org.usvm.uctx class ULazyRegistersStackModel( private val model: KModel, private val addressesMapping: AddressesMapping, - private val translator: UExprTranslator<*, *>, -) : URegistersStackEvaluator { + private val translator: UExprTranslator<*>, +) : UReadOnlyRegistersStack { private val uctx = translator.ctx override fun readRegister( - registerIndex: Int, + index: Int, sort: Sort, ): UExpr { - val registerReading = uctx.mkRegisterReading(registerIndex, sort) + val registerReading = uctx.mkRegisterReading(index, sort) val translated = translator.translate(registerReading) return model.eval(translated, isComplete = true).mapAddress(addressesMapping) } @@ -64,7 +46,7 @@ class ULazyRegistersStackModel( class ULazyIndexedMockModel( private val model: KModel, private val addressesMapping: AddressesMapping, - private val translator: UExprTranslator<*, *>, + private val translator: UExprTranslator<*>, ) : UMockEvaluator { override fun eval(symbol: UMockSymbol): UExpr { require(symbol is UIndexedMethodReturnValue<*, Sort>) @@ -73,166 +55,6 @@ class ULazyIndexedMockModel( } } -/** - * - * A lazy immutable heap model. Firstly, searches for decoded [UMemoryRegion], decodes it from [model] if not found, - * 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 happened, so this heap won't be mutated. - * - * @param model to decode from. It has to be detached. - * @param translator an expression translator used for encoding constraints. - * Provides initial symbolic values by [URegionId]s. - */ -class ULazyHeapModel( - private val model: KModel, - private val addressesMapping: AddressesMapping, - private val translator: UExprTranslator, -) : USymbolicHeap { - private val resolvedInputFields = mutableMapOf>() - private val resolvedInputArrays = mutableMapOf>() - private val resolvedInputLengths = mutableMapOf>() - - /** - * To resolve nullRef, we need to: - * * translate it - * * evaluate the translated value in the [model] - * * map the evaluated value with the [addressesMapping] - * - * Actually, its address should always be equal 0. - */ - private val nullRef = model - .eval(translator.translate(translator.ctx.nullRef)) - .mapAddress(addressesMapping) as UConcreteHeapRef - - init { - check(nullRef.address == NULL_ADDRESS) - } - - 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) { "Unexpected ref: $ref" } - - val resolvedRegion = resolvedInputFields[field] - val regionId = UInputFieldId(field, sort, contextHeap = null) - val initialValue = translator.translateInputFieldId(regionId) - - return when { - resolvedRegion != null -> resolvedRegion.read(ref).asExpr(sort) - else -> { - val region = UMemory1DArray(initialValue, model, addressesMapping) - resolvedInputFields[field] = region - 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) { "Unexpected ref: $ref" } - - val key = ref to index - - val resolvedRegion = resolvedInputArrays[arrayType] - val regionId = UInputArrayId(arrayType, sort, contextHeap = null) - val initialValue = translator.translateInputArrayId(regionId) - - return when { - resolvedRegion != null -> resolvedRegion.read(key).asExpr(sort) - else -> { - val region = UMemory2DArray(initialValue, model, addressesMapping) - resolvedInputArrays[arrayType] = region - 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) { "Unexpected ref: $ref" } - - val resolvedRegion = resolvedInputLengths[arrayType] - val regionId = UInputArrayLengthId(arrayType, ref.uctx.sizeSort, contextHeap = null) - val initialValue = translator.translateInputArrayLengthId(regionId) - - return when { - resolvedRegion != null -> resolvedRegion.read(ref) - else -> { - val region = UMemory1DArray(initialValue.cast(), model, addressesMapping) - resolvedInputLengths[arrayType] = region - region.read(ref) - } - } - } - - override fun writeField( - ref: UHeapRef, - field: Field, - sort: Sort, - value: UExpr, - guard: UBoolExpr, - ) = error("Illegal operation for a model heap") - - override fun writeArrayIndex( - ref: UHeapRef, - index: USizeExpr, - type: ArrayType, - sort: Sort, - value: UExpr, - guard: UBoolExpr, - ) = error("Illegal operation for a model heap") - - override fun writeArrayLength(ref: UHeapRef, size: USizeExpr, arrayType: ArrayType) = - error("Illegal operation for a model heap") - - 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 heap") - - 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 heap") - - override fun allocateArray(count: USizeExpr) = error("Illegal operation for a model heap") - - override fun allocateArrayInitialized( - type: ArrayType, - sort: Sort, - contents: Sequence>, - ) = error("Illegal operation for a model heap") - - override fun nullRef(): UConcreteHeapRef = nullRef - - override fun toMutableHeap(): ULazyHeapModel = this -} - /** * If [this] value is an instance of address expression, returns * an expression with a corresponding concrete address, otherwise @@ -244,4 +66,4 @@ fun UExpr.mapAddress( 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/model/Model.kt b/usvm-core/src/main/kotlin/org/usvm/model/Model.kt index 8a49fbfca..c47fe04dd 100644 --- a/usvm-core/src/main/kotlin/org/usvm/model/Model.kt +++ b/usvm-core/src/main/kotlin/org/usvm/model/Model.kt @@ -1,21 +1,23 @@ package org.usvm.model -import io.ksmt.utils.asExpr -import org.usvm.UArrayIndexLValue -import org.usvm.UArrayLengthLValue +import io.ksmt.utils.uncheckedCast +import org.usvm.INITIAL_INPUT_ADDRESS +import org.usvm.UBoolExpr import org.usvm.UComposer import org.usvm.UConcreteHeapRef import org.usvm.UContext import org.usvm.UExpr -import org.usvm.UFieldLValue import org.usvm.UHeapRef -import org.usvm.ULValue import org.usvm.UMockEvaluator -import org.usvm.URegisterLValue import org.usvm.USort -import org.usvm.memory.UReadOnlySymbolicHeap -import org.usvm.memory.UReadOnlySymbolicMemory -import org.usvm.types.UTypeStream +import org.usvm.memory.ULValue +import org.usvm.memory.UMemoryRegion +import org.usvm.memory.UMemoryRegionId +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.memory.UReadOnlyRegistersStack +import org.usvm.memory.URegisterStackId +import org.usvm.memory.UWritableMemory +import org.usvm.sampleUValue interface UModel { fun eval(expr: UExpr): UExpr @@ -29,14 +31,15 @@ interface UModel { * If a symbol from an expression not found inside the model, components return the default value * of the correct sort. */ -open class UModelBase( +open class UModelBase( ctx: UContext, - val stack: ULazyRegistersStackModel, - val heap: UReadOnlySymbolicHeap, - val types: UTypeModel, - val mocks: UMockEvaluator, -) : UModel, UReadOnlySymbolicMemory { - private val composer = UComposer(ctx, stack, heap, types, mocks) + override val stack: UReadOnlyRegistersStack, + override val types: UTypeModel, + override val mocker: UMockEvaluator, + internal val regions: Map, UReadOnlyMemoryRegion<*, *>>, + internal val nullRef: UConcreteHeapRef, +) : UModel, UWritableMemory { + private val composer = UComposer(ctx, this) /** * The evaluator supports only expressions with symbols inheriting [org.usvm.USymbol]. @@ -47,20 +50,48 @@ open class UModelBase( override fun eval(expr: UExpr): UExpr = composer.compose(expr) - @Suppress("UNCHECKED_CAST") - override fun read(lvalue: ULValue): UExpr = with(lvalue) { - when (this) { - is URegisterLValue -> stack.readRegister(idx, sort) - is UFieldLValue<*> -> heap.readField(ref, field as Field, sort).asExpr(sort) - is UArrayIndexLValue<*> -> heap.readArrayIndex(ref, index, arrayType as Type, sort).asExpr(sort) - is UArrayLengthLValue<*> -> heap.readArrayLength(ref, arrayType as Type) - - else -> throw IllegalArgumentException("Unexpected lvalue $this") + override fun getRegion(regionId: UMemoryRegionId): UReadOnlyMemoryRegion { + if (regionId is URegisterStackId) { + return stack.uncheckedCast() } + return regions[regionId]?.uncheckedCast() + ?: DefaultRegion(regionId, eval(regionId.sort.sampleUValue())) + } + + override fun nullRef(): UHeapRef = nullRef + + override fun toWritableMemory(): UWritableMemory = this + + override fun setRegion( + regionId: UMemoryRegionId, + newRegion: UMemoryRegion + ) { + error("Illegal operation for a model") + } + + override fun write(lvalue: ULValue, rvalue: UExpr, guard: UBoolExpr) { + error("Illegal operation for a model") } - override fun typeStreamOf(ref: UHeapRef): UTypeStream { - require(ref is UConcreteHeapRef) - return types.typeStream(ref) + override fun alloc(type: Type): UConcreteHeapRef { + error("Illegal operation for a model") } -} \ No newline at end of file + + private class DefaultRegion( + private val regionId: UMemoryRegionId, + private val value: UExpr + ) : UReadOnlyMemoryRegion { + override fun read(key: Key): UExpr = value + } +} + +fun modelEnsureConcreteInputRef(ref: UHeapRef): UConcreteHeapRef? { + // 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 + if (ref is UConcreteHeapRef && ref.address <= INITIAL_INPUT_ADDRESS) { + return ref + } + + return null +} diff --git a/usvm-core/src/main/kotlin/org/usvm/model/ModelRegions.kt b/usvm-core/src/main/kotlin/org/usvm/model/ModelRegions.kt index eb6560fcc..f5318d641 100644 --- a/usvm-core/src/main/kotlin/org/usvm/model/ModelRegions.kt +++ b/usvm-core/src/main/kotlin/org/usvm/model/ModelRegions.kt @@ -135,4 +135,4 @@ class UMemory2DArray internal return UMemory2DArray(stores.toPersistentMap(), constValue) } } -} \ No newline at end of file +} diff --git a/usvm-core/src/main/kotlin/org/usvm/model/UTypeModel.kt b/usvm-core/src/main/kotlin/org/usvm/model/UTypeModel.kt index 66dc0d3a2..5cad45c63 100644 --- a/usvm-core/src/main/kotlin/org/usvm/model/UTypeModel.kt +++ b/usvm-core/src/main/kotlin/org/usvm/model/UTypeModel.kt @@ -64,4 +64,9 @@ class UTypeModel( else -> error("Expecting concrete ref, but got $ref") } + + override fun getTypeStream(ref: UHeapRef): UTypeStream { + check(ref is UConcreteHeapRef) { "Unexpected ref: $ref" } + return typeStream(ref) + } } diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/ExceptionPropagationPathSelector.kt b/usvm-core/src/main/kotlin/org/usvm/ps/ExceptionPropagationPathSelector.kt index 0535147d9..95792f0eb 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/ExceptionPropagationPathSelector.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/ExceptionPropagationPathSelector.kt @@ -7,7 +7,7 @@ import java.util.IdentityHashMap /** * A class designed to give the highest priority to the states containing exceptions. */ -class ExceptionPropagationPathSelector>( +class ExceptionPropagationPathSelector>( private val selector: UPathSelector, ) : UPathSelector { // An internal queue for states containing exceptions. diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt index c26e82d38..5458ae734 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/PathSelectorFactory.kt @@ -12,7 +12,7 @@ import org.usvm.util.RandomizedPriorityCollection import kotlin.math.max import kotlin.random.Random -fun > createPathSelector( +fun > createPathSelector( initialState: State, options: UMachineOptions, coverageStatistics: () -> CoverageStatistics? = { null }, @@ -87,16 +87,16 @@ fun > creat /** * Wraps the selector into an [ExceptionPropagationPathSelector] if [propagateExceptions] is true. */ -private fun > UPathSelector.wrapIfRequired(propagateExceptions: Boolean) = +private fun > UPathSelector.wrapIfRequired(propagateExceptions: Boolean) = if (propagateExceptions && this !is ExceptionPropagationPathSelector) { ExceptionPropagationPathSelector(this) } else { this } -private fun > compareById(): Comparator = compareBy { it.id } +private fun > compareById(): Comparator = compareBy { it.id } -private fun > createDepthPathSelector(random: Random? = null): UPathSelector { +private fun > createDepthPathSelector(random: Random? = null): UPathSelector { if (random == null) { return WeightedPathSelector( priorityCollectionFactory = { DeterministicPriorityCollection(Comparator.naturalOrder()) }, @@ -111,7 +111,7 @@ private fun > createDepthPathSelector(rando ) } -private fun > createClosestToUncoveredPathSelector( +private fun > createClosestToUncoveredPathSelector( coverageStatistics: CoverageStatistics, distanceStatistics: DistanceStatistics, random: Random? = null, @@ -137,7 +137,7 @@ private fun > createForkDepthPathSelector( +private fun > createForkDepthPathSelector( random: Random? = null, ): UPathSelector { if (random == null) { diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/RandomTreePathSelector.kt b/usvm-core/src/main/kotlin/org/usvm/ps/RandomTreePathSelector.kt index f6d47d2c4..3883ba577 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/RandomTreePathSelector.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/RandomTreePathSelector.kt @@ -20,7 +20,7 @@ import java.util.IdentityHashMap * @param randomNonNegativeInt function returning non negative random integer used to select the next child in tree. * @param ignoreToken token to visit only the subtree of not removed states. Should be different for different consumers. */ -internal class RandomTreePathSelector, Statement>( +internal class RandomTreePathSelector, Statement>( private val root: PathsTrieNode, private val randomNonNegativeInt: () -> Int, private val ignoreToken: Long = 0, diff --git a/usvm-core/src/main/kotlin/org/usvm/ps/ShortestDistanceToTargetsStateWeighter.kt b/usvm-core/src/main/kotlin/org/usvm/ps/ShortestDistanceToTargetsStateWeighter.kt index 79b9e8eb4..9e2f28971 100644 --- a/usvm-core/src/main/kotlin/org/usvm/ps/ShortestDistanceToTargetsStateWeighter.kt +++ b/usvm-core/src/main/kotlin/org/usvm/ps/ShortestDistanceToTargetsStateWeighter.kt @@ -16,7 +16,7 @@ import kotlin.math.min * @param getCfgDistanceToExitPoint function with the following signature: * (method, stmt) -> shortest CFG distance from stmt to any of method's exit points. */ -class ShortestDistanceToTargetsStateWeighter>( +class ShortestDistanceToTargetsStateWeighter>( targets: Collection>, private val getCfgDistance: (Method, Statement, Statement) -> UInt, private val getCfgDistanceToExitPoint: (Method, Statement) -> UInt diff --git a/usvm-core/src/main/kotlin/org/usvm/solver/ExprTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/solver/ExprTranslator.kt index 89e28a0ab..4e10685ef 100644 --- a/usvm-core/src/main/kotlin/org/usvm/solver/ExprTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/solver/ExprTranslator.kt @@ -2,40 +2,57 @@ package org.usvm.solver import io.ksmt.decl.KDecl import io.ksmt.expr.KExpr -import io.ksmt.sort.KArray2Sort -import io.ksmt.sort.KArraySort import io.ksmt.sort.KBoolSort import io.ksmt.utils.mkConst +import io.ksmt.utils.uncheckedCast import org.usvm.UAddressSort -import org.usvm.UAllocatedArrayReading import org.usvm.UBoolSort +import org.usvm.UCollectionReading import org.usvm.UConcreteHeapRef import org.usvm.UContext import org.usvm.UExpr import org.usvm.UExprTransformer -import org.usvm.UHeapReading import org.usvm.UHeapRef import org.usvm.UIndexedMethodReturnValue -import org.usvm.UInputArrayLengthReading -import org.usvm.UInputArrayReading -import org.usvm.UInputFieldReading import org.usvm.UIsExpr import org.usvm.UIsSubtypeExpr import org.usvm.UIsSupertypeExpr import org.usvm.UMockSymbol import org.usvm.UNullRef import org.usvm.URegisterReading -import org.usvm.USizeExpr import org.usvm.USizeSort import org.usvm.USort import org.usvm.USymbol import org.usvm.USymbolicHeapRef -import org.usvm.memory.UAllocatedArrayId -import org.usvm.memory.UInputArrayId -import org.usvm.memory.UInputArrayLengthId -import org.usvm.memory.UInputFieldId -import org.usvm.memory.URegionId -import org.usvm.memory.USymbolicArrayIndex +import org.usvm.collection.array.UAllocatedArrayReading +import org.usvm.collection.array.UArrayRegionDecoder +import org.usvm.collection.array.UArrayRegionId +import org.usvm.collection.array.UInputArrayReading +import org.usvm.collection.array.USymbolicArrayId +import org.usvm.collection.array.length.UArrayLengthRegionDecoder +import org.usvm.collection.array.length.UArrayLengthsRegionId +import org.usvm.collection.array.length.UInputArrayLengthReading +import org.usvm.collection.array.length.USymbolicArrayLengthId +import org.usvm.collection.field.UFieldRegionDecoder +import org.usvm.collection.field.UFieldsRegionId +import org.usvm.collection.field.UInputFieldReading +import org.usvm.collection.field.USymbolicFieldId +import org.usvm.collection.map.length.UInputMapLengthReading +import org.usvm.collection.map.length.UMapLengthRegionDecoder +import org.usvm.collection.map.length.UMapLengthRegionId +import org.usvm.collection.map.length.USymbolicMapLengthId +import org.usvm.collection.map.primitive.UAllocatedMapReading +import org.usvm.collection.map.primitive.UInputMapReading +import org.usvm.collection.map.primitive.UMapRegionDecoder +import org.usvm.collection.map.primitive.UMapRegionId +import org.usvm.collection.map.ref.UAllocatedRefMapWithInputKeysReading +import org.usvm.collection.map.ref.UInputRefMapWithAllocatedKeysReading +import org.usvm.collection.map.ref.UInputRefMapWithInputKeysReading +import org.usvm.collection.map.ref.URefMapRegionDecoder +import org.usvm.collection.map.ref.URefMapRegionId +import org.usvm.collection.map.ref.USymbolicRefMapId +import org.usvm.memory.UMemoryRegionId +import org.usvm.util.Region import java.util.concurrent.ConcurrentHashMap /** @@ -44,9 +61,9 @@ import java.util.concurrent.ConcurrentHashMap * * To show semantics of the translator, we use [KExpr] as return values, though [UExpr] is a typealias for it. */ -open class UExprTranslator( +open class UExprTranslator( override val ctx: UContext, -) : UExprTransformer(ctx) { +) : UExprTransformer(ctx) { open fun translate(expr: UExpr): KExpr = apply(expr) override fun transform(expr: USymbol): KExpr = @@ -57,7 +74,7 @@ open class UExprTranslator( return registerConst } - override fun transform(expr: UHeapReading<*, *, *>): KExpr = + override fun transform(expr: UCollectionReading<*, *, *>): KExpr = error("You must override `transform` function in UExprTranslator for ${expr::class}") override fun transform(expr: UMockSymbol): KExpr = @@ -99,102 +116,142 @@ open class UExprTranslator( override fun transform(expr: UInputArrayLengthReading): KExpr = transformExprAfterTransformed(expr, expr.address) { address -> - val translator = inputArrayLengthIdTranslator(expr.region.regionId) - translator.translateReading(expr.region, address) + val translator = arrayLengthRegionDecoder(expr.collection.collectionId) + .inputArrayLengthRegionTranslator(expr.collection.collectionId) + translator.translateReading(expr.collection, address) } override fun transform(expr: UInputArrayReading): KExpr = transformExprAfterTransformed(expr, expr.address, expr.index) { address, index -> - val translator = inputArrayIdTranslator(expr.region.regionId) - translator.translateReading(expr.region, address to index) + val translator = arrayRegionDecoder(expr.collection.collectionId) + .inputArrayRegionTranslator(expr.collection.collectionId) + translator.translateReading(expr.collection, address to index) } override fun transform(expr: UAllocatedArrayReading): KExpr = transformExprAfterTransformed(expr, expr.index) { index -> - val translator = allocatedArrayIdTranslator(expr.region.regionId) - translator.translateReading(expr.region, index) + val translator = arrayRegionDecoder(expr.collection.collectionId) + .allocatedArrayRegionTranslator(expr.collection.collectionId) + translator.translateReading(expr.collection, index) } - override fun transform(expr: UInputFieldReading): KExpr = + override fun transform(expr: UInputFieldReading): KExpr = transformExprAfterTransformed(expr, expr.address) { address -> - val translator = inputFieldIdTranslator(expr.region.regionId) - translator.translateReading(expr.region, address) + val translator = fieldsRegionDecoder(expr.collection.collectionId) + .inputFieldRegionTranslator(expr.collection.collectionId) + translator.translateReading(expr.collection, address) } - fun translateAllocatedArrayId( - regionId: UAllocatedArrayId, - ): KExpr> = - with(ctx) { - val sort = mkArraySort(sizeSort, regionId.sort) - val translatedDefaultValue = translate(regionId.defaultValue) - mkArrayConst(sort, translatedDefaultValue) + override fun > transform( + expr: UAllocatedMapReading + ): KExpr = transformExprAfterTransformed(expr, expr.key) { key -> + val symbolicMapRegionId = with(expr.collection.collectionId) { + UMapRegionId(keySort, sort, mapType, keyInfo) } - fun translateInputArrayLengthId( - regionId: UInputArrayLengthId, - ): KExpr> = - with(ctx) { - mkArraySort(addressSort, sizeSort).mkConst(regionId.toString()) // TODO: replace toString + val translator = getOrPutRegionDecoder(symbolicMapRegionId) { + UMapRegionDecoder(symbolicMapRegionId, this) + }.allocatedMapTranslator(expr.collection.collectionId) + + translator.translateReading(expr.collection, key) + } + + override fun > transform( + expr: UInputMapReading + ): KExpr = transformExprAfterTransformed(expr, expr.address, expr.key) { address, key -> + val symbolicMapRegionId = with(expr.collection.collectionId) { + UMapRegionId(keySort, sort, mapType, keyInfo) } - fun translateInputArrayId( - regionId: UInputArrayId, - ): KExpr> = - with(ctx) { - mkArraySort(addressSort, sizeSort, regionId.sort).mkConst(regionId.toString()) // TODO: replace toString + val translator = getOrPutRegionDecoder(symbolicMapRegionId) { + UMapRegionDecoder(symbolicMapRegionId, this) + }.inputMapTranslator(expr.collection.collectionId) + + translator.translateReading(expr.collection, address to key) + } + + override fun transform( + expr: UAllocatedRefMapWithInputKeysReading + ): UExpr = transformExprAfterTransformed(expr, expr.keyRef) { keyRef -> + val translator = refMapRegionDecoder(expr.collection.collectionId) + .allocatedRefMapWithInputKeysTranslator(expr.collection.collectionId) + translator.translateReading(expr.collection, keyRef) + } + + override fun transform( + expr: UInputRefMapWithAllocatedKeysReading + ): UExpr = transformExprAfterTransformed(expr, expr.mapRef) { mapRef -> + val translator = refMapRegionDecoder(expr.collection.collectionId) + .inputRefMapWithAllocatedKeysTranslator(expr.collection.collectionId) + translator.translateReading(expr.collection, mapRef) + } + + override fun transform( + expr: UInputRefMapWithInputKeysReading + ): UExpr = transformExprAfterTransformed(expr, expr.mapRef, expr.keyRef) { mapRef, keyRef -> + val translator = refMapRegionDecoder(expr.collection.collectionId) + .inputRefMapTranslator(expr.collection.collectionId) + translator.translateReading(expr.collection, mapRef to keyRef) + } + + override fun transform(expr: UInputMapLengthReading): KExpr = + transformExprAfterTransformed(expr, expr.address) { address -> + val translator = mapLengthRegionDecoder(expr.collection.collectionId) + .inputMapLengthRegionTranslator(expr.collection.collectionId) + translator.translateReading(expr.collection, address) } - fun translateInputFieldId( - regionId: UInputFieldId, - ): KExpr> = - with(ctx) { - mkArraySort(addressSort, regionId.sort).mkConst(regionId.toString()) + fun > fieldsRegionDecoder( + fieldId: FieldId + ): UFieldRegionDecoder { + val fieldRegionId = UFieldsRegionId(fieldId.field, fieldId.sort) + return getOrPutRegionDecoder(fieldRegionId) { + UFieldRegionDecoder(fieldRegionId, this) } + } - private val regionIdToTranslator = ConcurrentHashMap, URegionTranslator<*, *, *, *>>() - - private inline fun > getOrPutRegionTranslator( - regionId: URegionId<*, *, *>, - defaultValue: () -> V, - ): V = regionIdToTranslator.getOrPut(regionId, defaultValue) as V - - private fun inputFieldIdTranslator( - regionId: UInputFieldId, - ): URegionTranslator, UHeapRef, Sort, *> = - getOrPutRegionTranslator(regionId) { - require(regionId.defaultValue == null) - val initialValue = translateInputFieldId(regionId) - val updateTranslator = U1DUpdatesTranslator(this, initialValue) - URegionTranslator(updateTranslator) + fun > arrayRegionDecoder( + arrayId: ArrayId + ): UArrayRegionDecoder { + val arrayRegionId = UArrayRegionId(arrayId.arrayType, arrayId.sort) + return getOrPutRegionDecoder(arrayRegionId) { + UArrayRegionDecoder(arrayRegionId, this) } + } - private fun allocatedArrayIdTranslator( - regionId: UAllocatedArrayId, - ): URegionTranslator, USizeExpr, Sort, *> = - getOrPutRegionTranslator(regionId) { - requireNotNull(regionId.defaultValue) - val initialValue = translateAllocatedArrayId(regionId) - val updateTranslator = U1DUpdatesTranslator(this, initialValue) - URegionTranslator(updateTranslator) + fun > arrayLengthRegionDecoder( + arrayLengthId: ArrayLenId + ): UArrayLengthRegionDecoder { + val arrayRegionId = UArrayLengthsRegionId(arrayLengthId.sort, arrayLengthId.arrayType) + return getOrPutRegionDecoder(arrayRegionId) { + UArrayLengthRegionDecoder(arrayRegionId, this) } + } - private fun inputArrayIdTranslator( - regionId: UInputArrayId, - ): URegionTranslator, USymbolicArrayIndex, Sort, *> = - getOrPutRegionTranslator(regionId) { - require(regionId.defaultValue == null) - val initialValue = translateInputArrayId(regionId) - val updateTranslator = U2DUpdatesTranslator(this, initialValue) - URegionTranslator(updateTranslator) + fun > refMapRegionDecoder( + refMapId: MapId + ): URefMapRegionDecoder { + val symbolicRefMapRegionId = URefMapRegionId(refMapId.sort, refMapId.mapType) + return getOrPutRegionDecoder(symbolicRefMapRegionId) { + URefMapRegionDecoder(symbolicRefMapRegionId, this) } + } - private fun inputArrayLengthIdTranslator( - regionId: UInputArrayLengthId, - ): URegionTranslator, UHeapRef, USizeSort, *> = - getOrPutRegionTranslator(regionId) { - require(regionId.defaultValue == null) - val initialValue = translateInputArrayLengthId(regionId) - val updateTranslator = U1DUpdatesTranslator(this, initialValue) - URegionTranslator(updateTranslator) + fun > mapLengthRegionDecoder( + mapLengthId: MapLengthId + ): UMapLengthRegionDecoder { + val symbolicMapLengthRegionId = UMapLengthRegionId(mapLengthId.sort, mapLengthId.mapType) + return getOrPutRegionDecoder(symbolicMapLengthRegionId) { + UMapLengthRegionDecoder(symbolicMapLengthRegionId, this) } + } + + val regionIdToDecoder: MutableMap, URegionDecoder<*, *>> = ConcurrentHashMap() + + inline fun > getOrPutRegionDecoder( + regionId: UMemoryRegionId<*, *>, + buildDecoder: () -> D + ): D = regionIdToDecoder.getOrPut(regionId) { + buildDecoder() + }.uncheckedCast() } diff --git a/usvm-core/src/main/kotlin/org/usvm/solver/RegionTranslator.kt b/usvm-core/src/main/kotlin/org/usvm/solver/RegionTranslator.kt index 7efdd8db3..f6290093a 100644 --- a/usvm-core/src/main/kotlin/org/usvm/solver/RegionTranslator.kt +++ b/usvm-core/src/main/kotlin/org/usvm/solver/RegionTranslator.kt @@ -1,36 +1,37 @@ package org.usvm.solver +import io.ksmt.KContext import io.ksmt.expr.KExpr +import io.ksmt.solver.KModel import io.ksmt.sort.KArray2Sort import io.ksmt.sort.KArraySort +import org.usvm.UConcreteHeapRef import org.usvm.UExpr +import org.usvm.UHeapRef import org.usvm.USort -import org.usvm.memory.UArrayId import org.usvm.memory.UMemoryUpdatesVisitor import org.usvm.memory.UPinpointUpdateNode import org.usvm.memory.URangedUpdateNode -import org.usvm.memory.URegionId -import org.usvm.memory.USymbolicMemoryRegion +import org.usvm.memory.UReadOnlyMemoryRegion +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.USymbolicCollectionId import org.usvm.memory.UUpdateNode import org.usvm.uctx -import java.util.IdentityHashMap /** * [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): KExpr { - val translated = translate(region) - return updateTranslator.visitSelect(translated, key) - } +interface URegionTranslator, Key, Sort : USort> { + fun translateReading(region: USymbolicCollection, key: Key): KExpr +} - private val visitorCache = IdentityHashMap() +interface URegionDecoder { + fun decodeLazyRegion(model: KModel, mapping: Map): UReadOnlyMemoryRegion +} - private fun translate(region: USymbolicMemoryRegion): Result = - region.updates.accept(updateTranslator, visitorCache) +interface UCollectionDecoder { + fun decodeCollection(model: KModel, mapping: Map): UReadOnlyMemoryRegion } /** @@ -39,9 +40,9 @@ class URegionTranslator, Key, Sort : U * @param exprTranslator defines how to perform translation on inner values. * @param initialValue defines an initial value for a translated array. */ -internal class U1DUpdatesTranslator( - private val exprTranslator: UExprTranslator<*, *>, - private val initialValue: KExpr>, +abstract class U1DUpdatesTranslator( + val exprTranslator: UExprTranslator<*>, + val initialValue: KExpr>, ) : UMemoryUpdatesVisitor, Sort, KExpr>> { /** @@ -69,32 +70,21 @@ internal class U1DUpdatesTranslator( // previous.store(key, mkIte(guard, value, previous.select(key))) } - is URangedUpdateNode<*, *, *, *> -> { - @Suppress("UNCHECKED_CAST") + is URangedUpdateNode<*, *, UExpr, Sort> -> { when (update.guard) { falseExpr -> previous - else -> { - (update as URangedUpdateNode, Any?, UExpr, Sort>) - val key = mkFreshConst("k", previous.sort.domain) - - val from = update.region - - val keyMapper = from.regionId.keyMapper(exprTranslator) - val convertedKey = keyMapper(update.keyConverter.convert(key)) - val isInside = update.includesSymbolically(key).translated // already includes guard - val result = from.regionId.instantiate( - from as USymbolicMemoryRegion, - convertedKey - ).translated - val ite = mkIte(isInside, result, previous.select(key)) - mkArrayLambda(key.decl, ite) - } + else -> translateRangedUpdate(previous, update) } } } } - private val UExpr.translated get() = exprTranslator.translate(this) + abstract fun KContext.translateRangedUpdate( + previous: KExpr>, + update: URangedUpdateNode<*, *, UExpr, Sort> + ): KExpr> + + val UExpr.translated get() = exprTranslator.translate(this) } /** @@ -103,13 +93,9 @@ internal class U1DUpdatesTranslator( * @param exprTranslator defines how to perform translation on inner values. * @param initialValue defines an initial value for a translated array. */ -internal class U2DUpdatesTranslator< - Key1Sort : USort, - Key2Sort : USort, - Sort : USort, - >( - private val exprTranslator: UExprTranslator<*, *>, - private val initialValue: KExpr>, +abstract class U2DUpdatesTranslator( + val exprTranslator: UExprTranslator<*>, + val initialValue: KExpr>, ) : UMemoryUpdatesVisitor, UExpr>, Sort, KExpr>> { /** @@ -136,30 +122,19 @@ internal class U2DUpdatesTranslator< mkIte(guard, previous.store(key1, key2, value), previous) } - is URangedUpdateNode<*, *, *, *> -> { - @Suppress("UNCHECKED_CAST") + is URangedUpdateNode<*, *, Pair, UExpr>, Sort> -> { when (update.guard) { falseExpr -> previous - else -> { - (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 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 = region.regionId.instantiate( - region as USymbolicMemoryRegion, - convertedKey - ).translated - val ite = mkIte(isInside, result, previous.select(key1, key2)) - mkArrayLambda(key1.decl, key2.decl, ite) - } + else -> translateRangedUpdate(previous, update) } } } } - private val UExpr.translated get() = exprTranslator.translate(this) + abstract fun KContext.translateRangedUpdate( + previous: KExpr>, + update: URangedUpdateNode<*, *, Pair, UExpr>, Sort> + ): KExpr> + + val UExpr.translated get() = exprTranslator.translate(this) } diff --git a/usvm-core/src/main/kotlin/org/usvm/solver/Solver.kt b/usvm-core/src/main/kotlin/org/usvm/solver/Solver.kt index c313b4264..0305caea7 100644 --- a/usvm-core/src/main/kotlin/org/usvm/solver/Solver.kt +++ b/usvm-core/src/main/kotlin/org/usvm/solver/Solver.kt @@ -11,7 +11,6 @@ import org.usvm.constraints.UEqualityConstraints import org.usvm.constraints.UPathConstraints import org.usvm.isFalse import org.usvm.isTrue -import org.usvm.memory.UMemoryBase import org.usvm.model.UModelBase import org.usvm.model.UModelDecoder @@ -29,14 +28,14 @@ abstract class USolver { abstract fun check(query: Query): USolverResult } -open class USolverBase( +open class USolverBase( protected val ctx: Context, protected val smtSolver: KSolver<*>, protected val typeSolver: UTypeSolver, - protected val translator: UExprTranslator, - protected val decoder: UModelDecoder, UModelBase>, - protected val softConstraintsProvider: USoftConstraintsProvider, -) : USolver, UModelBase>(), AutoCloseable { + protected val translator: UExprTranslator, + protected val decoder: UModelDecoder>, + protected val softConstraintsProvider: USoftConstraintsProvider, +) : USolver, UModelBase>(), AutoCloseable { protected fun translateLogicalConstraints(constraints: Iterable) { for (constraint in constraints) { @@ -99,7 +98,7 @@ open class USolverBase( translateLogicalConstraints(pc.logicalConstraints) } - override fun check(query: UPathConstraints): USolverResult> = + override fun check(query: UPathConstraints): USolverResult> = internalCheck(query, useSoftConstraints = false) fun checkWithSoftConstraints( @@ -110,7 +109,7 @@ open class USolverBase( private fun internalCheck( pc: UPathConstraints, useSoftConstraints: Boolean, - ): USolverResult> { + ): USolverResult> { if (pc.isFalse) { return UUnsatResult() } @@ -167,9 +166,10 @@ open class USolverBase( UModelBase( ctx, uModel.stack, - uModel.heap, typeResult.model, - uModel.mocks + uModel.mocker, + uModel.regions, + uModel.nullRef ) ) @@ -213,8 +213,8 @@ open class USolverBase( pop() } - fun emptyModel(): UModelBase = - (checkWithSoftConstraints(UPathConstraints(ctx)) as USatResult>).model + fun emptyModel(): UModelBase = + (checkWithSoftConstraints(UPathConstraints(ctx)) as USatResult>).model override fun close() { smtSolver.close() diff --git a/usvm-core/src/main/kotlin/org/usvm/solver/USoftConstraintsProvider.kt b/usvm-core/src/main/kotlin/org/usvm/solver/USoftConstraintsProvider.kt index 10d313193..425c26633 100644 --- a/usvm-core/src/main/kotlin/org/usvm/solver/USoftConstraintsProvider.kt +++ b/usvm-core/src/main/kotlin/org/usvm/solver/USoftConstraintsProvider.kt @@ -22,17 +22,20 @@ import io.ksmt.sort.KSortVisitor import io.ksmt.sort.KUninterpretedSort import io.ksmt.utils.asExpr import org.usvm.UAddressSort -import org.usvm.UAllocatedArrayReading +import org.usvm.collection.array.UAllocatedArrayReading +import org.usvm.collection.map.primitive.UAllocatedMapReading import org.usvm.UBoolExpr import org.usvm.UBvSort +import org.usvm.UCollectionReading import org.usvm.UConcreteHeapRef import org.usvm.UContext import org.usvm.UExpr -import org.usvm.UHeapReading import org.usvm.UIndexedMethodReturnValue -import org.usvm.UInputArrayLengthReading -import org.usvm.UInputArrayReading -import org.usvm.UInputFieldReading +import org.usvm.collection.array.length.UInputArrayLengthReading +import org.usvm.collection.array.UInputArrayReading +import org.usvm.collection.field.UInputFieldReading +import org.usvm.collection.map.length.UInputMapLengthReading +import org.usvm.collection.map.primitive.UInputMapReading import org.usvm.UIsSubtypeExpr import org.usvm.UIsSupertypeExpr import org.usvm.UMockSymbol @@ -42,9 +45,13 @@ import org.usvm.USizeExpr import org.usvm.USort import org.usvm.USymbol import org.usvm.UTransformer +import org.usvm.collection.map.ref.UAllocatedRefMapWithInputKeysReading +import org.usvm.collection.map.ref.UInputRefMapWithAllocatedKeysReading +import org.usvm.collection.map.ref.UInputRefMapWithInputKeysReading import org.usvm.uctx +import org.usvm.util.Region -class USoftConstraintsProvider(override val ctx: UContext) : UTransformer { +class USoftConstraintsProvider(override val ctx: UContext) : UTransformer { // We have a list here since sometimes we want to add several soft constraints // to make it possible to drop only a part of them, not the whole soft constraint private val caches = hashMapOf, Set>() @@ -83,7 +90,7 @@ class USoftConstraintsProvider(override val ctx: UContext) : UTrans override fun transform(expr: URegisterReading): UExpr = transformExpr(expr) override fun transform( - expr: UHeapReading<*, *, *>, + expr: UCollectionReading<*, *, *>, ): UExpr = error("You must override `transform` function in UExprTranslator for ${expr::class}") override fun transform( @@ -104,6 +111,16 @@ class USoftConstraintsProvider(override val ctx: UContext) : UTrans override fun transform(expr: UIsSupertypeExpr): UBoolExpr = expr + override fun transform(expr: UInputFieldReading): UExpr = + readingWithSingleArgumentTransform(expr, expr.address) + + override fun transform(expr: UAllocatedArrayReading): UExpr = + readingWithSingleArgumentTransform(expr, expr.index) + + override fun transform( + expr: UInputArrayReading, + ): UExpr = readingWithTwoArgumentsTransform(expr, expr.index, expr.address) + override fun transform( expr: UInputArrayLengthReading, ): USizeExpr = computeSideEffect(expr) { @@ -115,26 +132,39 @@ class USoftConstraintsProvider(override val ctx: UContext) : UTrans } } + override fun > transform( + expr: UAllocatedMapReading + ): UExpr = readingWithSingleArgumentTransform(expr, expr.key) + + override fun > transform( + expr: UInputMapReading + ): UExpr = readingWithTwoArgumentsTransform(expr, expr.key, expr.address) + override fun transform( - expr: UInputArrayReading, - ): UExpr = computeSideEffect(expr) { - val constraints = mutableSetOf() + expr: UAllocatedRefMapWithInputKeysReading + ): UExpr = readingWithSingleArgumentTransform(expr, expr.keyRef) - constraints += provide(expr.index) - constraints += provide(expr.address) - constraints += expr.sort.accept(sortPreferredValuesProvider)(expr) + override fun transform( + expr: UInputRefMapWithAllocatedKeysReading + ): UExpr = readingWithSingleArgumentTransform(expr, expr.mapRef) - caches[expr] = constraints - } + override fun transform( + expr: UInputRefMapWithInputKeysReading + ): UExpr = readingWithTwoArgumentsTransform(expr, expr.mapRef, expr.keyRef) - override fun transform(expr: UAllocatedArrayReading): UExpr = - readingWithSingleArgumentTransform(expr, expr.index) + override fun transform( + expr: UInputMapLengthReading + ): USizeExpr = computeSideEffect(expr) { + with(expr.ctx) { + val addressConstraints = provide(expr.address) + val mapLength = mkBvSignedLessOrEqualExpr(expr, PREFERRED_MAX_ARRAY_SIZE.toBv()) - override fun transform(expr: UInputFieldReading): UExpr = - readingWithSingleArgumentTransform(expr, expr.address) + caches[expr] = addressConstraints + mapLength + } + } private fun readingWithSingleArgumentTransform( - expr: UHeapReading<*, *, Sort>, + expr: UCollectionReading<*, *, Sort>, arg: UExpr<*>, ): UExpr = computeSideEffect(expr) { val argConstraint = provide(arg) @@ -143,6 +173,20 @@ class USoftConstraintsProvider(override val ctx: UContext) : UTrans caches[expr] = argConstraint + selfConstraint } + private fun readingWithTwoArgumentsTransform( + expr: UCollectionReading<*, *, Sort>, + arg0: UExpr<*>, + arg1: UExpr<*>, + ): UExpr = computeSideEffect(expr) { + val constraints = mutableSetOf() + + constraints += provide(arg0) + constraints += provide(arg1) + constraints += expr.sort.accept(sortPreferredValuesProvider)(expr) + + caches[expr] = constraints + } + // region KExpressions override fun transform(expr: KBvSignedLessOrEqualExpr): KExpr = with(expr.ctx) { diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt index 71f95bf94..f22eb6924 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/CoverageStatistics.kt @@ -13,7 +13,7 @@ import java.util.concurrent.ConcurrentHashMap * @param methods methods to track coverage of. * @param applicationGraph [ApplicationGraph] used to retrieve statements by method. */ -class CoverageStatistics>( +class CoverageStatistics>( methods: Set, private val applicationGraph: ApplicationGraph ) : UMachineObserver { diff --git a/usvm-core/src/main/kotlin/org/usvm/statistics/TerminatedStateRemover.kt b/usvm-core/src/main/kotlin/org/usvm/statistics/TerminatedStateRemover.kt index fc7b7f322..c1c922b52 100644 --- a/usvm-core/src/main/kotlin/org/usvm/statistics/TerminatedStateRemover.kt +++ b/usvm-core/src/main/kotlin/org/usvm/statistics/TerminatedStateRemover.kt @@ -10,7 +10,7 @@ import org.usvm.UState * it won't remove terminated states from the path trie. * It costs additional memory, but might be useful for debug purposes. */ -class TerminatedStateRemover> : UMachineObserver { +class TerminatedStateRemover> : UMachineObserver { override fun onStateTerminated(state: State) { state.pathLocation.states.remove(state) } diff --git a/usvm-core/src/main/kotlin/org/usvm/types/TypeRegion.kt b/usvm-core/src/main/kotlin/org/usvm/types/TypeRegion.kt index f91aff227..6b775eae4 100644 --- a/usvm-core/src/main/kotlin/org/usvm/types/TypeRegion.kt +++ b/usvm-core/src/main/kotlin/org/usvm/types/TypeRegion.kt @@ -263,6 +263,10 @@ class UTypeRegion( ) } + override fun union(other: UTypeRegion): UTypeRegion { + TODO("Not yet implemented") + } + private fun clone( typeStream: UTypeStream, supertypes: PersistentSet = this.supertypes, diff --git a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt index 48365a688..63322af8a 100644 --- a/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/CompositionTest.kt @@ -13,29 +13,31 @@ import io.mockk.spyk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.usvm.api.writeArrayIndex +import org.usvm.api.writeArrayLength +import org.usvm.api.writeField import org.usvm.constraints.UTypeEvaluator -import org.usvm.memory.UAllocatedArrayId -import org.usvm.memory.UAllocatedArrayRegion -import org.usvm.memory.UFlatUpdates -import org.usvm.memory.UInputArrayId -import org.usvm.memory.UInputArrayLengthId -import org.usvm.memory.UInputArrayLengthRegion -import org.usvm.memory.UInputArrayRegion -import org.usvm.memory.UInputFieldId -import org.usvm.memory.UInputFieldRegion -import org.usvm.memory.UInputToInputKeyConverter -import org.usvm.memory.UMemoryUpdates +import org.usvm.memory.UMemory import org.usvm.memory.UPinpointUpdateNode import org.usvm.memory.URangedUpdateNode -import org.usvm.memory.UReadOnlySymbolicHeap -import org.usvm.memory.URegionHeap -import org.usvm.memory.URegistersStackEvaluator -import org.usvm.memory.USymbolicArrayIndex +import org.usvm.memory.UReadOnlyMemory +import org.usvm.memory.UReadOnlyRegistersStack import org.usvm.memory.UUpdateNode -import org.usvm.memory.emptyAllocatedArrayRegion -import org.usvm.memory.emptyInputArrayRegion -import org.usvm.model.UHeapEagerModel +import org.usvm.memory.UFlatUpdates +import org.usvm.memory.USymbolicCollection +import org.usvm.memory.USymbolicCollectionUpdates +import org.usvm.collection.array.USymbolicArrayInputToInputCopyAdapter +import org.usvm.collection.array.UAllocatedArrayId +import org.usvm.collection.array.UAllocatedArrayReading +import org.usvm.collection.array.UInputArrayId +import org.usvm.collection.array.UInputArrayReading +import org.usvm.collection.array.length.UInputArrayLengthId +import org.usvm.collection.field.UInputFieldId +import org.usvm.collection.array.USymbolicArrayIndex +import org.usvm.collection.array.USymbolicArrayIndexKeyInfo +import org.usvm.model.UModelBase import org.usvm.model.URegistersStackEagerModel +import org.usvm.util.SetRegion import kotlin.reflect.KClass import kotlin.test.assertEquals import kotlin.test.assertIs @@ -43,28 +45,32 @@ import kotlin.test.assertSame import kotlin.test.assertTrue internal class CompositionTest { - private lateinit var stackEvaluator: URegistersStackEvaluator - private lateinit var heapEvaluator: UReadOnlySymbolicHeap + private lateinit var stackEvaluator: UReadOnlyRegistersStack private lateinit var typeEvaluator: UTypeEvaluator private lateinit var mockEvaluator: UMockEvaluator + private lateinit var memory: UReadOnlyMemory private lateinit var ctx: UContext private lateinit var concreteNull: UConcreteHeapRef - private lateinit var composer: UComposer + private lateinit var composer: UComposer @BeforeEach fun initializeContext() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents<*> = mockk() every { components.mkTypeSystem(any()) } returns mockk() ctx = UContext(components) concreteNull = ctx.mkConcreteHeapRef(NULL_ADDRESS) stackEvaluator = mockk() - heapEvaluator = mockk() typeEvaluator = mockk() mockEvaluator = mockk() - composer = UComposer(ctx, stackEvaluator, heapEvaluator, typeEvaluator, mockEvaluator) + memory = mockk() + every { memory.types } returns typeEvaluator + every { memory.stack } returns stackEvaluator + every { memory.mocker } returns mockEvaluator + + composer = UComposer(ctx, memory) } @Test @@ -181,7 +187,7 @@ internal class CompositionTest { val expression = mockk>() val bvValue = 32.toBv() - every { expression.accept(any()) } answers { (firstArg() as UComposer<*, *>).transform(expression) } + every { expression.accept(any()) } answers { (firstArg() as UComposer<*>).transform(expression) } every { mockEvaluator.eval(expression) } returns bvValue as UExpr val composedExpression = composer.compose(expression) as UExpr<*> @@ -197,9 +203,6 @@ internal class CompositionTest { val heapRef = mockk(relaxed = true) val type = mockk>(relaxed = true) // TODO replace with jacoDB type - val typeEvaluator = mockk>>() // TODO replace with jacoDB type - val heapEvaluator = mockk>>() // TODO replace with jacoDB type - val composer = UComposer(ctx, stackEvaluator, heapEvaluator, typeEvaluator, mockEvaluator) // TODO remove val isExpression = ctx.mkIsSubtypeExpr(heapRef, type) @@ -223,18 +226,16 @@ internal class CompositionTest { val fstResultValue = 1.toBv() val sndResultValue = 2.toBv() - val updates = UFlatUpdates( - symbolicEq = { k1, k2 -> k1 eq k2 }, - concreteCmp = { _, _ -> throw UnsupportedOperationException() }, - symbolicCmp = { _, _ -> throw UnsupportedOperationException() } - ).write(fstAddress, fstResultValue, guard = trueExpr) + val keyInfo = object : TestKeyInfo> { + override fun eqSymbolic(ctx: UContext, key1: UHeapRef, key2: UHeapRef): UBoolExpr = key1 eq key2 + } + + val updates = UFlatUpdates(keyInfo) + .write(fstAddress, fstResultValue, guard = trueExpr) .write(sndAddress, sndResultValue, guard = trueExpr) - val regionId = UInputArrayLengthId(arrayType, bv32Sort, contextHeap = null) - val regionArray = UInputArrayLengthRegion( - regionId, - updates, - ) + val collectionId = UInputArrayLengthId(arrayType, bv32Sort) + val regionArray = USymbolicCollection(collectionId, updates) val fstConcreteAddress = mkConcreteHeapRef(firstAddress) val sndConcreteAddress = mkConcreteHeapRef(secondAddress) @@ -245,13 +246,12 @@ internal class CompositionTest { val fstValueFromHeap = 42.toBv() val sndValueFromHeap = 43.toBv() - val heapToComposeWith = URegionHeap>>(ctx) + val heapToComposeWith = UMemory>, Any>(ctx, mockk()) heapToComposeWith.writeArrayLength(fstConcreteAddress, fstValueFromHeap, arrayType) heapToComposeWith.writeArrayLength(sndConcreteAddress, sndValueFromHeap, arrayType) - val typeEvaluator = mockk>>>() - val composer = UComposer(ctx, stackEvaluator, heapToComposeWith, typeEvaluator, mockEvaluator) + val composer = UComposer(ctx, heapToComposeWith) every { fstAddress.accept(composer) } returns fstConcreteAddress every { sndAddress.accept(composer) } returns sndConcreteAddress @@ -274,17 +274,20 @@ internal class CompositionTest { mkAnd((k1.first == k2.first).expr, (k1.second == k2.second).expr) } - val updates = UFlatUpdates( - symbolicCmp = { _, _ -> shouldNotBeCalled() }, - concreteCmp = { k1, k2 -> k1 == k2 }, - symbolicEq = { k1, k2 -> keyEqualityComparer(k1, k2) } - ).write(fstAddress to fstIndex, 42.toBv(), guard = trueExpr) + val keyInfo = object : TestKeyInfo> { + override fun cmpConcreteLe(key1: USymbolicArrayIndex, key2: USymbolicArrayIndex): Boolean = key1 == key2 + override fun eqSymbolic(ctx: UContext, key1: USymbolicArrayIndex, key2: USymbolicArrayIndex): UBoolExpr = + keyEqualityComparer(key1, key2) + } + + val updates = UFlatUpdates(keyInfo) + .write(fstAddress to fstIndex, 42.toBv(), guard = trueExpr) .write(sndAddress to sndIndex, 43.toBv(), guard = trueExpr) val arrayType: KClass> = Array::class - val region = UInputArrayRegion( - UInputArrayId(arrayType, bv32Sort, contextHeap = null), + val region = USymbolicCollection( + UInputArrayId(arrayType, bv32Sort), updates, ) @@ -295,12 +298,7 @@ internal class CompositionTest { val answer = 43.toBv() - val typeEvaluator = mockk>>() // TODO replace with jacoDB type - val heapEvaluator = URegionHeap>(ctx) // TODO replace with jacoDB type - - val composer = UComposer( - ctx, stackEvaluator, heapEvaluator, typeEvaluator, mockEvaluator - ) // TODO replace with jacoDB type + val composer = UComposer(ctx, UMemory, Any>(ctx, mockk())) // TODO replace with jacoDB type every { fstAddress.accept(composer) } returns sndAddress every { fstIndex.accept(composer) } returns sndIndex @@ -322,30 +320,25 @@ internal class CompositionTest { val sndAddress = mkRegisterReading(2, addressSort) val sndIndex = mkRegisterReading(3, sizeSort) + // TODO replace with jacoDB type val arrayType: KClass> = Array::class // Create an empty region - val region = emptyInputArrayRegion(arrayType, mkBv32Sort()) + val region = UInputArrayId(arrayType, mkBv32Sort()).emptyRegion() - // TODO replace with jacoDB type // create a reading from the region val fstArrayIndexReading = mkInputArrayReading(region, fstAddress, fstIndex) - val typeEvaluator = mockk>>() // TODO replace with jacoDB type - val sndHeapEvaluator = URegionHeap>(ctx) // TODO replace with jacoDB type + val sndMemory = UMemory, Any>(ctx, mockk(), mockk()) // create a heap with a record: (sndAddress, sndIndex) = 2 - sndHeapEvaluator.writeArrayIndex(sndAddress, sndIndex, arrayType, mkBv32Sort(), 2.toBv(), mkTrue()) + sndMemory.writeArrayIndex(sndAddress, sndIndex, arrayType, mkBv32Sort(), 2.toBv(), mkTrue()) - val sndComposer = UComposer( - ctx, stackEvaluator, sndHeapEvaluator, typeEvaluator, mockEvaluator - ) // TODO replace with jacoDB type + val sndComposer = UComposer(ctx, sndMemory) - val fstEvaluator = URegionHeap>(ctx) // TODO replace with jacoDB type + val fstMemory = UMemory, Any>(ctx, mockk(), mockk()) // create a heap with a record: (fstAddress, fstIndex) = 1 - fstEvaluator.writeArrayIndex(fstAddress, fstIndex, arrayType, mkBv32Sort(), 1.toBv(), mkTrue()) + fstMemory.writeArrayIndex(fstAddress, fstIndex, arrayType, mkBv32Sort(), 1.toBv(), mkTrue()) - val fstComposer = UComposer( - ctx, stackEvaluator, fstEvaluator, typeEvaluator, mockEvaluator - ) // TODO replace with jacoDB type + val fstComposer = UComposer(ctx, fstMemory) // TODO replace with jacoDB type // Both heaps leave everything untouched every { sndAddress.accept(sndComposer) } returns sndAddress @@ -366,7 +359,7 @@ internal class CompositionTest { .write(USymbolicArrayIndex(sndAddress, sndIndex), 2.toBv(), guard = mkTrue()) require(fstComposedExpr is UInputArrayReading<*, *>) - assert(fstComposedExpr.region.updates.toList() == expectedRegion.updates.toList()) + assert(fstComposedExpr.collection.updates.toList() == expectedRegion.updates.toList()) } @Test @@ -380,16 +373,17 @@ internal class CompositionTest { val fstSymbolicIndex = mockk() val sndSymbolicIndex = mockk() - val updates = UFlatUpdates( - symbolicEq = { k1, k2 -> k1 eq k2 }, - concreteCmp = { _, _ -> throw UnsupportedOperationException() }, - symbolicCmp = { _, _ -> throw UnsupportedOperationException() } - ).write(fstIndex, 1.toBv(), guard = trueExpr) + val keyInfo = object : TestKeyInfo> { + override fun eqSymbolic(ctx: UContext, key1: USizeExpr, key2: USizeExpr): UBoolExpr = key1 eq key2 + } + + val updates = UFlatUpdates(keyInfo) + .write(fstIndex, 1.toBv(), guard = trueExpr) .write(sndIndex, 2.toBv(), guard = trueExpr) - val regionId = UAllocatedArrayId(arrayType, bv32Sort, mkBv(0), address, contextHeap = null) - val regionArray = UAllocatedArrayRegion( - regionId, + val collectionId = UAllocatedArrayId(arrayType, bv32Sort, address) + val regionArray = USymbolicCollection( + collectionId, updates, ) @@ -403,7 +397,7 @@ internal class CompositionTest { val fstValue = 42.toBv() val sndValue = 43.toBv() - val heapToComposeWith = URegionHeap>>(ctx) + val heapToComposeWith = UMemory>, Any>(ctx, mockk()) heapToComposeWith.writeArrayIndex( fstAddressForCompose, concreteIndex, arrayType, regionArray.sort, fstValue, guard = trueExpr @@ -412,8 +406,7 @@ internal class CompositionTest { sndAddressForCompose, concreteIndex, arrayType, regionArray.sort, sndValue, guard = trueExpr ) - val typeEvaluator = mockk>>>() - val composer = UComposer(ctx, stackEvaluator, heapToComposeWith, typeEvaluator, mockEvaluator) + val composer = UComposer(ctx, heapToComposeWith) every { fstSymbolicIndex.accept(composer) } returns concreteIndex every { sndSymbolicIndex.accept(composer) } returns concreteIndex @@ -434,22 +427,20 @@ internal class CompositionTest { val symbolicIndex = mockk() val symbolicAddress = mkRegisterReading(0, addressSort) - val regionArray = emptyAllocatedArrayRegion(arrayType, 0, addressSort) + val regionArray = UAllocatedArrayId(arrayType, addressSort, 0) + .emptyRegion() .write(mkBv(0), symbolicAddress, trueExpr) .write(mkBv(1), mkConcreteHeapRef(1), trueExpr) val reading = mkAllocatedArrayReading(regionArray, symbolicIndex) val concreteNullRef = mkConcreteHeapRef(NULL_ADDRESS) - val heapToComposeWith = UHeapEagerModel>>( - concreteNullRef, - emptyMap(), - emptyMap(), - emptyMap() + + val heapToComposeWith = UModelBase>>( + ctx, mockk(), mockk(), mockk(), emptyMap(), concreteNullRef ) - val typeEvaluator = mockk>>>() - val composer = spyk(UComposer(ctx, stackEvaluator, heapToComposeWith, typeEvaluator, mockEvaluator)) + val composer = spyk(UComposer(ctx, heapToComposeWith)) every { symbolicIndex.accept(composer) } returns mkBv(2) every { symbolicAddress.accept(composer) } returns mkConcreteHeapRef(-1) @@ -464,16 +455,17 @@ internal class CompositionTest { val aAddress = mockk() val bAddress = mockk() - val updates = UFlatUpdates( - symbolicEq = { k1, k2 -> (k1 == k2).expr }, - concreteCmp = { _, _ -> throw UnsupportedOperationException() }, - symbolicCmp = { _, _ -> throw UnsupportedOperationException() } - ) + val keyInfo = object : TestKeyInfo> { + override fun eqSymbolic(ctx: UContext, key1: UHeapRef, key2: UHeapRef): UBoolExpr = + (key1 == key2).expr + } + + val updates = UFlatUpdates(keyInfo) val field = mockk() // TODO replace with jacoDB field // An empty region with one write in it - val region = UInputFieldRegion( - UInputFieldId(field, bv32Sort, contextHeap = null), + val region = USymbolicCollection( + UInputFieldId(field, bv32Sort), updates, ).write(aAddress, 43.toBv(), guard = trueExpr) @@ -492,13 +484,11 @@ internal class CompositionTest { */ val answer = 43.toBv() - // TODO replace with jacoDB type - val heapEvaluator = URegionHeap(ctx) - heapEvaluator.writeField(aAddress, field, bv32Sort, 42.toBv(), guard = trueExpr) + val composeMemory = UMemory(ctx, mockk()) + composeMemory.writeField(aAddress, field, bv32Sort, 42.toBv(), guard = trueExpr) - // TODO replace with jacoDB type - val composer = UComposer(ctx, stackEvaluator, heapEvaluator, typeEvaluator, mockEvaluator) + val composer = UComposer(ctx, composeMemory) val composedExpression = composer.compose(expression) @@ -508,10 +498,12 @@ internal class CompositionTest { @Test fun testHeapRefEq() = with(ctx) { val concreteNull = ctx.mkConcreteHeapRef(NULL_ADDRESS) - val stackModel = - URegistersStackEagerModel(concreteNull, mapOf(0 to mkConcreteHeapRef(-1), 1 to mkConcreteHeapRef(-2))) + val stackModel = URegistersStackEagerModel( + concreteNull, mapOf(0 to mkConcreteHeapRef(-1), 1 to mkConcreteHeapRef(-2)) + ) + val model = UModelBase(ctx, stackModel, mockk(), mockk(), emptyMap(), concreteNull) - val composer = UComposer(this, stackModel, mockk(), mockk(), mockk()) + val composer = UComposer(this, model) val heapRefEvalEq = mkHeapRefEq(mkRegisterReading(0, addressSort), mkRegisterReading(1, addressSort)) @@ -523,10 +515,11 @@ internal class CompositionTest { fun testHeapRefNullAddress() = with(ctx) { val stackModel = URegistersStackEagerModel(concreteNull, mapOf(0 to mkConcreteHeapRef(0))) - val heapEvaluator: UReadOnlySymbolicHeap = mockk() - every { heapEvaluator.nullRef() } returns concreteNull + val composedMemory: UReadOnlyMemory = mockk() + every { composedMemory.nullRef() } returns concreteNull + every { composedMemory.stack } returns stackModel - val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + val composer = UComposer(this, composedMemory) val heapRefEvalEq = mkHeapRefEq(mkRegisterReading(0, addressSort), nullRef) @@ -538,37 +531,46 @@ internal class CompositionTest { fun testComposeComplexRangedUpdate() = with(ctx) { val arrayType = mockk() - val regionHeap = URegionHeap(ctx) + val symbolicRef0 = mkRegisterReading(0, addressSort) as UHeapRef + val symbolicRef1 = mkRegisterReading(1, addressSort) as UHeapRef + val symbolicRef2 = mkRegisterReading(2, addressSort) as UHeapRef + val composedSymbolicHeapRef = mkConcreteHeapRef(1) - val symbolicRef0 = mkRegisterReading(0, addressSort) - val symbolicRef1 = mkRegisterReading(1, addressSort) - val symbolicRef2 = mkRegisterReading(2, addressSort) - val composedSymbolicHeapRef = ctx.mkConcreteHeapRef(1) + val composeMemory = UMemory(ctx, mockk()) - regionHeap.writeArrayIndex(composedSymbolicHeapRef, mkBv(3), arrayType, bv32Sort, mkBv(1337), trueExpr) + composeMemory.writeArrayIndex(composedSymbolicHeapRef, mkBv(3), arrayType, bv32Sort, mkBv(1337), trueExpr) - val stackModel = URegistersStackEagerModel( - concreteNull, mapOf( - 0 to composedSymbolicHeapRef, - 1 to composedSymbolicHeapRef, - 2 to composedSymbolicHeapRef, - 3 to ctx.mkRegisterReading(3, bv32Sort), - ) - ) - val composer = UComposer(this, stackModel, regionHeap, mockk(), mockk()) + composeMemory.stack.push(4) + composeMemory.stack.writeRegister(0, composedSymbolicHeapRef) + composeMemory.stack.writeRegister(1, composedSymbolicHeapRef) + composeMemory.stack.writeRegister(2, composedSymbolicHeapRef) + composeMemory.stack.writeRegister(3, mkRegisterReading(3, bv32Sort)) + + val composer = UComposer(ctx, composeMemory) - val fromRegion0 = emptyInputArrayRegion(arrayType, bv32Sort) + val fromRegion0 = UInputArrayId(arrayType, bv32Sort) + .emptyRegion() .write(symbolicRef0 to mkBv(0), mkBv(42), trueExpr) - val keyConverter1 = UInputToInputKeyConverter(symbolicRef0 to mkBv(0), symbolicRef1 to mkBv(0), mkBv(5)) + val adapter1 = USymbolicArrayInputToInputCopyAdapter( + symbolicRef0 to mkSizeExpr(0), + symbolicRef1 to mkSizeExpr(0), + symbolicRef1 to mkSizeExpr(5), + USymbolicArrayIndexKeyInfo + ) val fromRegion1 = fromRegion0 - .copyRange(fromRegion0, symbolicRef0 to mkBv(0), symbolicRef0 to mkBv(5), keyConverter1, trueExpr) + .copyRange(fromRegion0, adapter1, trueExpr) - val keyConverter2 = UInputToInputKeyConverter(symbolicRef1 to mkBv(0), symbolicRef2 to mkBv(0), mkBv(5)) + val adapter2 = USymbolicArrayInputToInputCopyAdapter( + symbolicRef1 to mkSizeExpr(0), + symbolicRef2 to mkSizeExpr(0), + symbolicRef2 to mkSizeExpr(5), + USymbolicArrayIndexKeyInfo + ) val fromRegion2 = fromRegion1 - .copyRange(fromRegion1, symbolicRef1 to mkBv(0), symbolicRef1 to mkBv(5), keyConverter2, trueExpr) + .copyRange(fromRegion1, adapter2, trueExpr) val idx0 = mkRegisterReading(3, bv32Sort) @@ -577,15 +579,15 @@ internal class CompositionTest { val composedExpr0 = composer.compose(reading0) val composedReading0 = assertIs>(composedExpr0) - fun UMemoryUpdates<*, *>.allUpdates(): Collection> = + fun USymbolicCollectionUpdates<*, *>.allUpdates(): Collection> = fold(mutableListOf()) { acc, r -> acc += r - acc += (r as? URangedUpdateNode<*, *, *, *>)?.region?.updates?.allUpdates() ?: emptyList() + acc += (r as? URangedUpdateNode<*, *, *, *>)?.sourceCollection?.updates?.allUpdates() ?: emptyList() acc } val pinpointUpdates = composedReading0 - .region + .collection .updates .allUpdates() .filterIsInstance>() @@ -596,15 +598,15 @@ internal class CompositionTest { @Test fun testNullRefRegionDefaultValue() = with(ctx) { - val heapEvaluator: UReadOnlySymbolicHeap = mockk() - - every { heapEvaluator.nullRef() } returns concreteNull + val composedMemory: UReadOnlyMemory = mockk() + every { composedMemory.nullRef() } returns concreteNull val stackModel = URegistersStackEagerModel(concreteNull, mapOf(0 to mkBv(0), 1 to mkBv(0), 2 to mkBv(2))) + every { composedMemory.stack } returns stackModel - val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + val composer = UComposer(this, composedMemory) - val region = emptyAllocatedArrayRegion(mockk(), 1, addressSort) + val region = UAllocatedArrayId(mockk(), addressSort, 1).emptyRegion() val reading = region.read(mkRegisterReading(0, sizeSort)) val expr = composer.compose(reading) diff --git a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt index 1be5723f6..54936cbdd 100644 --- a/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt +++ b/usvm-core/src/test/kotlin/org/usvm/TestUtil.kt @@ -1,8 +1,10 @@ package org.usvm import org.usvm.constraints.UPathConstraints -import org.usvm.memory.UMemoryBase +import org.usvm.memory.UMemory +import org.usvm.memory.USymbolicCollectionKeyInfo import org.usvm.model.UModelBase +import org.usvm.util.Region typealias Field = java.lang.reflect.Field typealias Type = kotlin.reflect.KClass<*> @@ -27,10 +29,21 @@ internal fun pseudoRandom(i: Int): Int { internal class TestState( ctx: UContext, callStack: UCallStack, pathConstraints: UPathConstraints, - memory: UMemoryBase, models: List>, + memory: UMemory, models: List>, pathLocation: PathsTrieNode, -) : UState(ctx, callStack, pathConstraints, memory, models, pathLocation) { +) : UState(ctx, callStack, pathConstraints, memory, models, pathLocation) { override fun clone(newConstraints: UPathConstraints?): TestState = this override val isExceptional = false -} \ No newline at end of file +} + +interface TestKeyInfo> : USymbolicCollectionKeyInfo { + override fun keyToRegion(key: T): Reg = shouldNotBeCalled() + override fun eqSymbolic(ctx: UContext, key1: T, key2: T): UBoolExpr = shouldNotBeCalled() + override fun eqConcrete(key1: T, key2: T): Boolean = shouldNotBeCalled() + override fun cmpSymbolicLe(ctx: UContext, key1: T, key2: T): UBoolExpr = shouldNotBeCalled() + override fun cmpConcreteLe(key1: T, key2: T): Boolean = shouldNotBeCalled() + override fun keyRangeRegion(from: T, to: T): Reg = shouldNotBeCalled() + override fun topRegion(): Reg = shouldNotBeCalled() + override fun bottomRegion(): Reg = shouldNotBeCalled() +} diff --git a/usvm-core/src/test/kotlin/org/usvm/UContextInterningTest.kt b/usvm-core/src/test/kotlin/org/usvm/UContextInterningTest.kt index 296129155..614cf2db2 100644 --- a/usvm-core/src/test/kotlin/org/usvm/UContextInterningTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/UContextInterningTest.kt @@ -4,10 +4,14 @@ import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.usvm.memory.UAllocatedArrayRegion -import org.usvm.memory.UInputArrayLengthRegion -import org.usvm.memory.UInputArrayRegion -import org.usvm.memory.UInputFieldRegion +import org.usvm.collection.array.UAllocatedArray +import org.usvm.collection.array.UAllocatedArrayReading +import org.usvm.collection.array.UInputArray +import org.usvm.collection.array.UInputArrayReading +import org.usvm.collection.array.length.UInputArrayLengthReading +import org.usvm.collection.array.length.UInputArrayLengths +import org.usvm.collection.field.UInputFieldReading +import org.usvm.collection.field.UInputFields import kotlin.test.assertTrue class UContextInterningTest { @@ -15,7 +19,7 @@ class UContextInterningTest { @BeforeEach fun initializeContext() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents<*> = mockk() every { components.mkTypeSystem(any()) } returns mockk() context = UContext(components) } @@ -61,8 +65,8 @@ class UContextInterningTest { @Test fun testFieldReadingInterning() = with(context) { - val fstRegion = mockk>() - val sndRegion = mockk>() + val fstRegion = mockk>() + val sndRegion = mockk>() every { fstRegion.sort } returns bv32Sort every { sndRegion.sort } returns boolSort @@ -86,8 +90,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 @@ -112,8 +116,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 @@ -143,8 +147,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 diff --git a/usvm-core/src/test/kotlin/org/usvm/api/collections/ObjectMapTest.kt b/usvm-core/src/test/kotlin/org/usvm/api/collections/ObjectMapTest.kt new file mode 100644 index 000000000..f292ef359 --- /dev/null +++ b/usvm-core/src/test/kotlin/org/usvm/api/collections/ObjectMapTest.kt @@ -0,0 +1,322 @@ +package org.usvm.api.collections + +import io.ksmt.solver.KSolver +import org.junit.jupiter.api.Disabled +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.api.collection.ObjectMapCollectionApi.mkSymbolicObjectMap +import org.usvm.api.collection.ObjectMapCollectionApi.symbolicObjectMapContains +import org.usvm.api.collection.ObjectMapCollectionApi.symbolicObjectMapGet +import org.usvm.api.collection.ObjectMapCollectionApi.symbolicObjectMapMergeInto +import org.usvm.api.collection.ObjectMapCollectionApi.symbolicObjectMapPut +import org.usvm.api.collection.ObjectMapCollectionApi.symbolicObjectMapRemove +import org.usvm.api.collection.ObjectMapCollectionApi.symbolicObjectMapSize +import org.usvm.model.UModelBase +import org.usvm.solver.USatResult +import org.usvm.types.single.SingleTypeSystem +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs + +@Disabled("Set collection implementation required") +class ObjectMapTest : SymbolicCollectionTestBase() { + + private val mapType = SingleTypeSystem.SingleType + + @Test + fun testConcreteMapContains() { + val concreteMap = state.mkSymbolicObjectMap(mapType) + testMapContains(concreteMap) + } + + @Test + fun testSymbolicMapContains() { + val symbolicMap = ctx.mkRegisterReading(99, ctx.addressSort) + testMapContains(symbolicMap) + } + + private fun testMapContains(mapRef: UHeapRef) { + val concreteKeys = (1..5).map { ctx.mkConcreteHeapRef(it) } + val symbolicKeys = (1..5).map { ctx.mkRegisterReading(it, ctx.addressSort) } + val storedConcrete = concreteKeys.dropLast(1) + val missedConcrete = concreteKeys.last() + val storedSymbolic = symbolicKeys.dropLast(1) + val missedSymbolic = symbolicKeys.last() + + fillMap(mapRef, storedConcrete + storedSymbolic, startValueIdx = 1) + + checkWithSolver { + (storedConcrete + storedSymbolic).forEach { key -> + assertImpossible { + val keyContains = state.symbolicObjectMapContains(mapRef, key, mapType) + keyContains eq falseExpr + } + } + + assertImpossible { + val keyContains = state.symbolicObjectMapContains(mapRef, missedConcrete, mapType) + keyContains eq trueExpr + } + + assertPossible { + val keyContains = state.symbolicObjectMapContains(mapRef, missedSymbolic, mapType) + keyContains eq falseExpr + } + } + + val removeConcrete = storedConcrete.first() + val removeSymbolic = storedSymbolic.first() + val removedKeys = listOf(removeConcrete, removeSymbolic) + removedKeys.forEach { key -> + state.symbolicObjectMapRemove(mapRef, key, mapType) + } + + checkWithSolver { + removedKeys.forEach { key -> + assertImpossible { + val keyContains = state.symbolicObjectMapContains(mapRef, key, mapType) + keyContains eq trueExpr + } + } + } + } + + @Test + fun testConcreteMapContainsComposition() { + val concreteMap = state.mkSymbolicObjectMap(mapType) + testMapContainsComposition(concreteMap) + } + + @Test + fun testSymbolicMapContainsComposition() { + val symbolicMap = ctx.mkRegisterReading(99, ctx.addressSort) + testMapContainsComposition(symbolicMap) + } + + private fun testMapContainsComposition(mapRef: UHeapRef) { + val concreteKeys = (1..5).map { ctx.mkConcreteHeapRef(it) } + val symbolicKeys = (1..5).map { ctx.mkRegisterReading(it, ctx.addressSort) } + val otherSymbolicKey = ctx.mkRegisterReading(symbolicKeys.size + 1, ctx.addressSort) + + fillMap(mapRef, concreteKeys + symbolicKeys, startValueIdx = 1) + + val otherKeyContains = state.symbolicObjectMapContains(mapRef, otherSymbolicKey, mapType) + state.pathConstraints += otherKeyContains + + val result = uSolver.checkWithSoftConstraints(state.pathConstraints) + assertIs>>(result) + + assertEquals(ctx.trueExpr, result.model.eval(otherKeyContains)) + + val removedKeys = setOf(concreteKeys.first(), symbolicKeys.first(), otherSymbolicKey) + removedKeys.forEach { key -> + state.symbolicObjectMapRemove(mapRef, key, mapType) + } + + val removedKeysValues = removedKeys.mapTo(hashSetOf()) { result.model.eval(it) } + (concreteKeys + symbolicKeys + otherSymbolicKey).forEach { key -> + val keyContains = state.symbolicObjectMapContains(mapRef, key, mapType) + val keyContainsValue = result.model.eval(keyContains) + val keyValue = result.model.eval(key) + + val expectedResult = ctx.mkBool(keyValue !in removedKeysValues) + assertEquals(expectedResult, keyContainsValue) + } + } + + @Test + fun testConcreteMapSize() { + val concreteMap = state.mkSymbolicObjectMap(mapType) + testMapSize(concreteMap) { size, lowerBound, upperBound -> + assertPossible { size eq upperBound } + assertPossible { size eq lowerBound } + assertImpossible { + mkBvSignedLessExpr(size, lowerBound) or mkBvSignedGreaterExpr(size, upperBound) + } + } + } + + @Test + fun testSymbolicMapSize() { + val symbolicMap = ctx.mkRegisterReading(99, ctx.addressSort) + testMapSize(symbolicMap) { size, lowerBound, upperBound -> + assertPossible { size eq lowerBound } + assertPossible { size eq upperBound } + } + } + + private fun testMapSize(mapRef: UHeapRef, checkSizeBounds: KSolver<*>.(USizeExpr, USizeExpr, USizeExpr) -> Unit) { + val concreteKeys = (1..5).map { ctx.mkConcreteHeapRef(it) } + val symbolicKeys = (1..5).map { ctx.mkRegisterReading(it, ctx.addressSort) } + + fillMap(mapRef, concreteKeys + symbolicKeys, startValueIdx = 1) + + checkWithSolver { + val sizeLowerBound = ctx.mkSizeExpr(concreteKeys.size + 1) // +1 for at least one symbolic key + val sizeUpperBound = ctx.mkSizeExpr(concreteKeys.size + symbolicKeys.size) + + val actualSize = state.symbolicObjectMapSize(mapRef, mapType) + + checkSizeBounds(actualSize, sizeLowerBound, sizeUpperBound) + } + + val removedKeys = setOf(concreteKeys.first(), symbolicKeys.first()) + removedKeys.forEach { key -> + state.symbolicObjectMapRemove(mapRef, key, mapType) + } + + checkWithSolver { + /** + * Size lower bound before remove: concrete size + 1 + * where we add 1 for at least one symbolic key + * + * Size after remove is concrete -1 since we remove 1 concrete key and one symbolic. + * */ + val minKeySize = concreteKeys.size - 1 + val sizeLowerBound = ctx.mkSizeExpr(minKeySize) + val sizeUpperBound = ctx.mkSizeExpr(concreteKeys.size + symbolicKeys.size - removedKeys.size) + + val actualSize = state.symbolicObjectMapSize(mapRef, mapType) + + checkSizeBounds(actualSize, sizeLowerBound, sizeUpperBound) + } + } + + @Test + fun testMapMergeSymbolicIntoConcrete() = with(state.memory) { + val concreteMap = state.mkSymbolicObjectMap(mapType) + val symbolicMap = ctx.mkRegisterReading(99, ctx.addressSort) + + testMapMerge(concreteMap, symbolicMap) + } + + @Test + fun testMapMergeConcreteIntoSymbolic() = with(state.memory) { + val concreteMap = state.mkSymbolicObjectMap(mapType) + val symbolicMap = ctx.mkRegisterReading(99, ctx.addressSort) + + testMapMerge(concreteMap, symbolicMap) + } + + @Test + fun testMapMergeConcreteIntoConcrete() = with(state.memory) { + val concreteMap0 = state.mkSymbolicObjectMap(mapType) + val concreteMap1 = state.mkSymbolicObjectMap(mapType) + + testMapMerge(concreteMap0, concreteMap1) + } + + @Test + fun testMapMergeSymbolicIntoSymbolic() = with(state.memory) { + val symbolicMap0 = ctx.mkRegisterReading(99, ctx.addressSort) + val symbolicMap1 = ctx.mkRegisterReading(999, ctx.addressSort) + + testMapMerge(symbolicMap0, symbolicMap1) + } + + private fun testMapMerge(mergeTarget: UHeapRef, otherMap: UHeapRef) { + val overlapConcreteKeys = (1..3).map { ctx.mkConcreteHeapRef(it) } + val nonOverlapConcreteKeys0 = (11..13).map { ctx.mkConcreteHeapRef(it) } + val nonOverlapConcreteKeys1 = (21..23).map { ctx.mkConcreteHeapRef(it) } + + val overlapSymbolicKeys = (31..33).map { ctx.mkRegisterReading(it, ctx.addressSort) } + val nonOverlapSymbolicKeys0 = (41..43).map { ctx.mkRegisterReading(it, ctx.addressSort) } + val nonOverlapSymbolicKeys1 = (51..53).map { ctx.mkRegisterReading(it, ctx.addressSort) } + + val tgtMapKeys = listOf( + overlapConcreteKeys, + nonOverlapConcreteKeys0, + overlapSymbolicKeys, + nonOverlapSymbolicKeys0 + ).flatten() + + val otherMapKeys = listOf( + overlapConcreteKeys, + nonOverlapConcreteKeys1, + overlapSymbolicKeys, + nonOverlapSymbolicKeys1 + ).flatten() + + val removedKeys = setOf( + nonOverlapConcreteKeys0.first(), + nonOverlapConcreteKeys1.first() + ) + + val tgtValues = fillMap(mergeTarget, tgtMapKeys, 256) + val otherValues = fillMap(otherMap, otherMapKeys, 65536) + + for (key in removedKeys) { + state.symbolicObjectMapRemove(mergeTarget, key, mapType) + state.symbolicObjectMapRemove(otherMap, key, mapType) + } + + state.symbolicObjectMapMergeInto(mergeTarget, otherMap, mapType, ctx.sizeSort) + + val mergedContains0 = tgtMapKeys.map { state.symbolicObjectMapContains(mergeTarget, it, mapType) } + val mergedContains1 = otherMapKeys.map { state.symbolicObjectMapContains(mergeTarget, it, mapType) } + + val mergedValues0 = tgtMapKeys.map { state.symbolicObjectMapGet(mergeTarget, it, mapType, ctx.sizeSort) } + val mergedValues1 = otherMapKeys.map { state.symbolicObjectMapGet(mergeTarget, it, mapType, ctx.sizeSort) } + + mergedContains0.forEach { checkNoConcreteHeapRefs(it) } + mergedContains1.forEach { checkNoConcreteHeapRefs(it) } + + mergedValues0.forEach { checkNoConcreteHeapRefs(it) } + mergedValues1.forEach { checkNoConcreteHeapRefs(it) } + + checkWithSolver { + val mergedNonOverlapKeys = listOf( + nonOverlapConcreteKeys0, + nonOverlapConcreteKeys1, + nonOverlapSymbolicKeys0, + nonOverlapSymbolicKeys1 + ).flatten() - removedKeys + + for (key in mergedNonOverlapKeys) { + val keyContains = state.symbolicObjectMapContains(mergeTarget, key, mapType) + assertPossible { keyContains eq trueExpr } + + val storedValue = tgtValues[key] ?: otherValues[key] ?: error("$key was not stored") + val actualValue: USizeExpr = state.symbolicObjectMapGet(mergeTarget, key, mapType, ctx.sizeSort) + assertPossible { storedValue eq actualValue } + } + + for (key in removedKeys) { + val keyContains = state.symbolicObjectMapContains(mergeTarget, key, mapType) + assertPossible { keyContains eq falseExpr } + } + + val overlapKeys = listOf( + overlapConcreteKeys, + overlapSymbolicKeys + ).flatten() + + for (key in overlapKeys) { + val keyContains = state.symbolicObjectMapContains(mergeTarget, key, mapType) + assertPossible { keyContains eq trueExpr } + + val storedV1 = tgtValues.getValue(key) + val storedV2 = otherValues.getValue(key) + val actualValue: USizeExpr = state.symbolicObjectMapGet(mergeTarget, key, mapType, ctx.sizeSort) + + assertPossible { + (actualValue eq storedV1) or (actualValue eq storedV2) + } + } + } + } + + private fun fillMap(mapRef: UHeapRef, keys: List, startValueIdx: Int) = with(state) { + keys.mapIndexed { index, key -> + val value = ctx.mkSizeExpr(index + startValueIdx) + symbolicObjectMapPut( + mapRef, + key, + value, + mapType, + ctx.sizeSort + ) + key to value + }.toMap() + } +} diff --git a/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt b/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt new file mode 100644 index 000000000..f12faadcc --- /dev/null +++ b/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicCollectionTestBase.kt @@ -0,0 +1,100 @@ +package org.usvm.api.collections + +import io.ksmt.solver.KSolver +import io.ksmt.solver.KSolverStatus +import io.ksmt.solver.z3.KZ3Solver +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.usvm.Type +import org.usvm.UBoolExpr +import org.usvm.UCallStack +import org.usvm.UComponents +import org.usvm.UContext +import org.usvm.UExpr +import org.usvm.UState +import org.usvm.constraints.UPathConstraints +import org.usvm.memory.UMemory +import org.usvm.model.buildTranslatorAndLazyDecoder +import org.usvm.solver.UExprTranslator +import org.usvm.solver.USoftConstraintsProvider +import org.usvm.solver.USolverBase +import org.usvm.solver.UTypeSolver +import org.usvm.types.single.SingleTypeSystem +import kotlin.test.assertEquals + +abstract class SymbolicCollectionTestBase { + lateinit var ctx: UContext + lateinit var pathConstraints: UPathConstraints + lateinit var memory: UMemory + lateinit var state: StateStub + lateinit var translator: UExprTranslator + lateinit var uSolver: USolverBase + + @BeforeEach + fun initializeContext() { + val components: UComponents = mockk() + every { components.mkTypeSystem(any()) } returns mockk() + + ctx = UContext(components) + pathConstraints = UPathConstraints(ctx) + memory = UMemory(ctx, pathConstraints.typeConstraints) + state = StateStub(ctx, pathConstraints, memory) + + val softConstraintProvider = USoftConstraintsProvider(ctx) + val (translator, decoder) = buildTranslatorAndLazyDecoder(ctx) + this.translator = translator + val typeSolver = UTypeSolver(SingleTypeSystem) + uSolver = USolverBase(ctx, KZ3Solver(ctx), typeSolver, translator, decoder, softConstraintProvider) + } + + class StateStub( + ctx: UContext, + pathConstraints: UPathConstraints, + memory: UMemory + ) : UState( + ctx, UCallStack(), + pathConstraints, memory, emptyList(), ctx.mkInitialLocation() + ) { + override fun clone(newConstraints: UPathConstraints?): StateStub { + error("Unsupported") + } + + override val isExceptional: Boolean + get() = false + } + + fun checkNoConcreteHeapRefs(expr: UExpr<*>) { + // Translator throws exception if concrete ref occurs + translator.translate(expr) + } + + inline fun checkWithSolver(body: KSolver<*>.() -> Unit) { + KZ3Solver(ctx).use { solver -> + solver.body() + } + } + + fun KSolver<*>.assertPossible(mkCheck: UContext.() -> UBoolExpr) = + assertStatus(KSolverStatus.SAT) { mkCheck() } + + fun KSolver<*>.assertImpossible(mkCheck: UContext.() -> UBoolExpr) = + assertStatus(KSolverStatus.UNSAT) { mkCheck() } + + fun KSolver<*>.assertStatus(status: KSolverStatus, mkCheck: UContext.() -> UBoolExpr) = try { + push() + + val expr = ctx.mkCheck() + val solverExpr = translator.translate(expr) + + assert(solverExpr) + + val actualStatus = check() + if (status != actualStatus) { + println() + } + assertEquals(status, actualStatus) + } finally { + pop() + } +} diff --git a/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicListTest.kt b/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicListTest.kt new file mode 100644 index 000000000..42dd5a8fb --- /dev/null +++ b/usvm-core/src/test/kotlin/org/usvm/api/collections/SymbolicListTest.kt @@ -0,0 +1,205 @@ +package org.usvm.api.collections + +import io.ksmt.solver.KSolver +import org.usvm.UContext +import org.usvm.UHeapRef +import org.usvm.USizeExpr +import org.usvm.api.collection.ListCollectionApi.mkSymbolicList +import org.usvm.api.collection.ListCollectionApi.symbolicListAdd +import org.usvm.api.collection.ListCollectionApi.symbolicListGet +import org.usvm.api.collection.ListCollectionApi.symbolicListInsert +import org.usvm.api.collection.ListCollectionApi.symbolicListRemove +import org.usvm.api.collection.ListCollectionApi.symbolicListSet +import org.usvm.api.collection.ListCollectionApi.symbolicListSize +import org.usvm.types.single.SingleTypeSystem +import kotlin.test.Test + +class SymbolicListTest : SymbolicCollectionTestBase() { + + private val listType = SingleTypeSystem.SingleType + + @Test + fun testConcreteListValues() { + val concreteList = state.mkSymbolicList(listType) + testListValues(concreteList) + } + + @Test + fun testSymbolicListValues() { + val symbolicList = ctx.mkRegisterReading(99, ctx.addressSort) + testListValues(symbolicList) + } + + private fun testListValues(listRef: UHeapRef) { + val initialSize = state.symbolicListSize(listRef, listType) + + val listValues = (1..5).mapTo(mutableListOf()) { ctx.mkSizeExpr(it) } + listValues.forEach { + state.symbolicListAdd(listRef, listType, ctx.sizeSort, it) + } + + checkValues(listRef, listValues, initialSize) + + val modifiedIdx = listValues.size / 2 + val modifiedValue = ctx.mkSizeExpr(42) + listValues[modifiedIdx] = modifiedValue + val modifiedListIdx = ctx.mkBvAddExpr(initialSize, ctx.mkSizeExpr(modifiedIdx)) + state.symbolicListSet(listRef, listType, ctx.sizeSort, modifiedListIdx, modifiedValue) + + checkValues(listRef, listValues, initialSize) + + val removeIdx = listValues.size / 2 + listValues.removeAt(removeIdx) + val removeListIdx = ctx.mkBvAddExpr(initialSize, ctx.mkSizeExpr(removeIdx)) + state.symbolicListRemove(listRef, listType, ctx.sizeSort, removeListIdx) + + checkValues(listRef, listValues, initialSize) + + val insertIdx = listValues.size / 2 + val insertValue = ctx.mkSizeExpr(17) + listValues.add(insertIdx, insertValue) + val insertListIdx = ctx.mkBvAddExpr(initialSize, ctx.mkSizeExpr(removeIdx)) + state.symbolicListInsert(listRef, listType, ctx.sizeSort, insertListIdx, insertValue) + + checkValues(listRef, listValues, initialSize) + } + + @Test + fun testConcreteListBoundModification() { + val concreteList = state.mkSymbolicList(listType) + testListBoundModification(concreteList) + } + + @Test + fun testSymbolicListBoundModification() { + val symbolicList = ctx.mkRegisterReading(99, ctx.addressSort) + testListBoundModification(symbolicList) + } + + private fun testListBoundModification(listRef: UHeapRef) { + val initialSize = state.symbolicListSize(listRef, listType) + + val listValues = (1..5).mapTo(mutableListOf()) { ctx.mkSizeExpr(it) } + listValues.forEach { + state.symbolicListAdd(listRef, listType, ctx.sizeSort, it) + } + + checkValues(listRef, listValues, initialSize) + + // remove first + listValues.removeAt(0) + state.symbolicListRemove(listRef, listType, ctx.sizeSort, initialSize) + + checkValues(listRef, listValues, initialSize) + + // insert first + val insertHeadValue = ctx.mkSizeExpr(17) + listValues.add(0, insertHeadValue) + state.symbolicListInsert(listRef, listType, ctx.sizeSort, initialSize, insertHeadValue) + + checkValues(listRef, listValues, initialSize) + + // remove last + listValues.removeAt(listValues.lastIndex) + run { + val listSize = state.symbolicListSize(listRef, listType) + state.symbolicListRemove(listRef, listType, ctx.sizeSort, ctx.mkBvSubExpr(listSize, ctx.mkSizeExpr(1))) + } + + checkValues(listRef, listValues, initialSize) + + // insert last + val insertTailValue = ctx.mkSizeExpr(17) + listValues.add(listValues.size, insertTailValue) + run { + val listSize = state.symbolicListSize(listRef, listType) + state.symbolicListInsert(listRef, listType, ctx.sizeSort, listSize, insertTailValue) + } + + checkValues(listRef, listValues, initialSize) + } + + private fun checkValues(listRef: UHeapRef, values: List, initialSize: USizeExpr) { + val listValues = values.indices.map { idx -> + val listIndex = ctx.mkBvAddExpr(initialSize, ctx.mkSizeExpr(idx)) + state.symbolicListGet(listRef, listIndex, listType, ctx.sizeSort) + } + checkWithSolver { + values.zip(listValues) { expectedValue, actualValue -> + assertImpossible { + mkAnd( + inputListSizeAssumption(initialSize), + actualValue neq expectedValue + ) + } + } + } + } + + @Test + fun testConcreteListSize() { + val concreteList = state.mkSymbolicList(listType) + testListSize(concreteList) { actualSize, expectedSize -> + assertImpossible { actualSize neq expectedSize } + } + } + + @Test + fun testSymbolicListSize() { + val symbolicList = ctx.mkRegisterReading(99, ctx.addressSort) + val initialSize = state.symbolicListSize(symbolicList, listType) + + testListSize(symbolicList) { actualSize, expectedSize -> + assertImpossible { + mkAnd( + inputListSizeAssumption(initialSize), + mkBvSignedLessExpr(actualSize, expectedSize) + ) + } + } + } + + private fun testListSize(listRef: UHeapRef, checkSize: KSolver<*>.(USizeExpr, USizeExpr) -> Unit) { + val numValues = 5 + repeat(numValues) { + state.symbolicListAdd(listRef, listType, ctx.sizeSort, ctx.mkSizeExpr(it)) + } + + checkWithSolver { + val actualSize = state.symbolicListSize(listRef, listType) + checkSize(actualSize, ctx.mkSizeExpr(numValues)) + } + + state.symbolicListInsert( + listRef = listRef, + listType = listType, + sort = ctx.sizeSort, + index = ctx.mkSizeExpr(0), + value = ctx.mkSizeExpr(17) + ) + + checkWithSolver { + val actualSize = state.symbolicListSize(listRef, listType) + checkSize(actualSize, ctx.mkSizeExpr(numValues + 1)) + } + + state.symbolicListRemove( + listRef = listRef, + listType = listType, + sort = ctx.sizeSort, + index = ctx.mkSizeExpr(numValues / 2) + ) + + checkWithSolver { + val actualSize = state.symbolicListSize(listRef, listType) + checkSize(actualSize, ctx.mkSizeExpr(numValues)) + } + } + + // Constraint size to avoid overflow + private fun UContext.inputListSizeAssumption(size: USizeExpr) = + mkAnd( + mkBvSignedGreaterOrEqualExpr(size, mkSizeExpr(0)), + mkBvSignedLessOrEqualExpr(size, mkSizeExpr(1000)), + ) +} diff --git a/usvm-core/src/test/kotlin/org/usvm/constraints/EqualityConstraintsTests.kt b/usvm-core/src/test/kotlin/org/usvm/constraints/EqualityConstraintsTests.kt index 313095f7f..dfb5a1f90 100644 --- a/usvm-core/src/test/kotlin/org/usvm/constraints/EqualityConstraintsTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/constraints/EqualityConstraintsTests.kt @@ -16,7 +16,7 @@ class EqualityConstraintsTests { @BeforeEach fun initializeContext() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents<*> = mockk() every { components.mkTypeSystem(any()) } returns mockk() ctx = UContext(components) constraints = UEqualityConstraints(ctx) diff --git a/usvm-core/src/test/kotlin/org/usvm/constraints/NumericConstraintsTests.kt b/usvm-core/src/test/kotlin/org/usvm/constraints/NumericConstraintsTests.kt index 149c0785f..17265ef04 100644 --- a/usvm-core/src/test/kotlin/org/usvm/constraints/NumericConstraintsTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/constraints/NumericConstraintsTests.kt @@ -32,7 +32,7 @@ class NumericConstraintsTests { @BeforeEach fun initializeContext() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents<*> = mockk() every { components.mkTypeSystem(any()) } returns mockk() ctx = UContext(components) bvSort = ctx.mkBvSort(sizeBits = 8u) diff --git a/usvm-core/src/test/kotlin/org/usvm/memory/HeapMemCpyTest.kt b/usvm-core/src/test/kotlin/org/usvm/memory/HeapMemCpyTest.kt new file mode 100644 index 000000000..922bba53e --- /dev/null +++ b/usvm-core/src/test/kotlin/org/usvm/memory/HeapMemCpyTest.kt @@ -0,0 +1,124 @@ +package org.usvm.memory + +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.usvm.* +import org.usvm.api.allocateArray +import org.usvm.api.memcpy +import org.usvm.api.readArrayIndex +import org.usvm.api.writeArrayIndex +import org.usvm.constraints.UEqualityConstraints +import org.usvm.constraints.UTypeConstraints +import kotlin.test.Test +import kotlin.test.assertEquals + +class HeapMemCpyTest { + private lateinit var ctx: UContext + private lateinit var heap: UMemory + private lateinit var arrayType: Type + private lateinit var arrayValueSort: USizeSort + + @BeforeEach + fun initializeContext() { + val components: UComponents = mockk() + every { components.mkTypeSystem(any()) } returns mockk() + ctx = UContext(components) + val eqConstraints = UEqualityConstraints(ctx) + val typeConstraints = UTypeConstraints(components.mkTypeSystem(ctx), eqConstraints) + heap = UMemory(ctx, typeConstraints) + arrayType = mockk() + arrayValueSort = ctx.sizeSort + } + + @Test + fun testMemCopyRemoveIndex() = with(ctx) { + val (array, ref) = initializeArray() + + val srcFrom = 4 + val dstFrom = 3 + val srcTo = array.size + val dstTo = dstFrom + (srcTo - srcFrom) + array.copyInto( + destination = array, + destinationOffset = dstFrom, + startIndex = srcFrom, + endIndex = srcTo + ) + + heap.memcpy( + srcRef = ref, + dstRef = ref, + type = arrayType, + elementSort = arrayValueSort, + fromSrcIdx = ctx.mkSizeExpr(srcFrom), + fromDstIdx = ctx.mkSizeExpr(dstFrom), + toDstIdx = ctx.mkSizeExpr(dstTo - 1), + guard = ctx.trueExpr + ) + + checkArrayEquals(ref, array) + } + + @Test + fun testMemCopyInsertIndex() = with(ctx) { + val (array, ref) = initializeArray() + + val srcFrom = 3 + val dstFrom = 4 + val srcTo = array.size - 1 + val dstTo = dstFrom + (srcTo - srcFrom) + array.copyInto( + destination = array, + destinationOffset = dstFrom, + startIndex = srcFrom, + endIndex = srcTo + ) + + heap.memcpy( + srcRef = ref, + dstRef = ref, + type = arrayType, + elementSort = arrayValueSort, + fromSrcIdx = ctx.mkSizeExpr(srcFrom), + fromDstIdx = ctx.mkSizeExpr(dstFrom), + toDstIdx = ctx.mkSizeExpr(dstTo - 1), + guard = ctx.trueExpr + ) + + checkArrayEquals(ref, array) + } + + private fun initializeArray(): Pair { + val array = IntArray(10) { it + 1 } + val ref = heap.allocateArray(arrayType, ctx.mkSizeExpr(array.size)) + + array.indices.forEach { idx -> + heap.writeArrayIndex( + ref = ref, + index = ctx.mkSizeExpr(idx), + type = arrayType, + sort = arrayValueSort, + value = ctx.mkSizeExpr(array[idx]), + guard = ctx.trueExpr + ) + } + + checkArrayEquals(ref, array) + + return array to ref + } + + private fun checkArrayEquals(ref: UHeapRef, expected: IntArray) { + val storedValues = expected.indices.map { idx -> + heap.readArrayIndex( + ref = ref, + index = ctx.mkSizeExpr(idx), + arrayType = arrayType, + sort = arrayValueSort + ) + } + + assertEquals(expected.map { ctx.mkSizeExpr(it) }, storedValues) + } +} diff --git a/usvm-core/src/test/kotlin/org/usvm/memory/HeapMemsetTest.kt b/usvm-core/src/test/kotlin/org/usvm/memory/HeapMemsetTest.kt index b3e47b897..70866dfd5 100644 --- a/usvm-core/src/test/kotlin/org/usvm/memory/HeapMemsetTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/memory/HeapMemsetTest.kt @@ -8,6 +8,13 @@ import org.usvm.Type import org.usvm.UAddressSort import org.usvm.UComponents import org.usvm.UContext +import org.usvm.api.allocateArray +import org.usvm.api.allocateArrayInitialized +import org.usvm.api.memset +import org.usvm.api.readArrayIndex +import org.usvm.api.readArrayLength +import org.usvm.constraints.UEqualityConstraints +import org.usvm.constraints.UTypeConstraints import org.usvm.sampleUValue import kotlin.test.Test import kotlin.test.assertEquals @@ -15,16 +22,18 @@ import kotlin.test.assertTrue class HeapMemsetTest { private lateinit var ctx: UContext - private lateinit var heap: URegionHeap + private lateinit var heap: UMemory private lateinit var arrayType: Type private lateinit var arrayValueSort: UAddressSort @BeforeEach fun initializeContext() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents = mockk() every { components.mkTypeSystem(any()) } returns mockk() ctx = UContext(components) - heap = URegionHeap(ctx) + val eqConstraints = UEqualityConstraints(ctx) + val typeConstraints = UTypeConstraints(components.mkTypeSystem(ctx), eqConstraints) + heap = UMemory(ctx, typeConstraints) arrayType = mockk() arrayValueSort = ctx.addressSort } @@ -34,7 +43,7 @@ class HeapMemsetTest { val concreteAddresses = (17..30).toList() val values = concreteAddresses.map { mkConcreteHeapRef(it) } - val ref = heap.allocateArray(mkSizeExpr(concreteAddresses.size)) + val ref = heap.allocateArray(arrayType, mkSizeExpr(concreteAddresses.size)) val initiallyStoredValues = concreteAddresses.indices.map { idx -> heap.readArrayIndex(ref, mkSizeExpr(idx), arrayType, arrayValueSort) } @@ -55,7 +64,7 @@ class HeapMemsetTest { val values = concreteAddresses.map { mkConcreteHeapRef(it) } val initialSize = concreteAddresses.size * 2 - val ref = heap.allocateArray(mkSizeExpr(initialSize)) + val ref = heap.allocateArray(arrayType, mkSizeExpr(initialSize)) val actualInitialSize = heap.readArrayLength(ref, arrayType) heap.memset(ref, arrayType, arrayValueSort, values.asSequence()) diff --git a/usvm-core/src/test/kotlin/org/usvm/memory/HeapRefEqTest.kt b/usvm-core/src/test/kotlin/org/usvm/memory/HeapRefEqTest.kt index cbfc19cc1..60898ac25 100644 --- a/usvm-core/src/test/kotlin/org/usvm/memory/HeapRefEqTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/memory/HeapRefEqTest.kt @@ -5,22 +5,22 @@ import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.usvm.Field import org.usvm.Type import org.usvm.UComponents import org.usvm.UContext +import org.usvm.api.allocate import kotlin.test.assertSame class HeapRefEqTest { private lateinit var ctx: UContext - private lateinit var heap: URegionHeap + private lateinit var heap: UMemory @BeforeEach fun initializeContext() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents = mockk() every { components.mkTypeSystem(any()) } returns mockk() ctx = UContext(components) - heap = URegionHeap(ctx) + heap = UMemory(ctx, mockk()) } @Test diff --git a/usvm-core/src/test/kotlin/org/usvm/memory/HeapRefSplittingTest.kt b/usvm-core/src/test/kotlin/org/usvm/memory/HeapRefSplittingTest.kt index 3cb8a1781..103437450 100644 --- a/usvm-core/src/test/kotlin/org/usvm/memory/HeapRefSplittingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/memory/HeapRefSplittingTest.kt @@ -12,8 +12,13 @@ import org.usvm.UAddressSort import org.usvm.UBv32Sort import org.usvm.UComponents import org.usvm.UContext -import org.usvm.UInputFieldReading +import org.usvm.collection.field.UInputFieldReading import org.usvm.UIteExpr +import org.usvm.api.allocate +import org.usvm.api.readArrayIndex +import org.usvm.api.readField +import org.usvm.api.writeArrayIndex +import org.usvm.api.writeField import kotlin.test.assertEquals import kotlin.test.assertIs import kotlin.test.assertNotNull @@ -21,7 +26,7 @@ import kotlin.test.assertSame class HeapRefSplittingTest { private lateinit var ctx: UContext - private lateinit var heap: URegionHeap + private lateinit var heap: UMemory private lateinit var valueFieldDescr: Pair private lateinit var addressFieldDescr: Pair @@ -29,10 +34,10 @@ class HeapRefSplittingTest { @BeforeEach fun initializeContext() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents = mockk() every { components.mkTypeSystem(any()) } returns mockk() ctx = UContext(components) - heap = URegionHeap(ctx) + heap = UMemory(ctx, mockk()) valueFieldDescr = mockk() to ctx.bv32Sort addressFieldDescr = mockk() to ctx.addressSort @@ -76,7 +81,7 @@ class HeapRefSplittingTest { assertEquals(cond, res.condition) assertSame(value, res.trueBranch) val reading = assertIs>(res.falseBranch) - assertEquals(!cond, reading.region.updates.single().guard) + assertEquals(!cond, reading.collection.updates.single().guard) } @Test @@ -155,7 +160,7 @@ class HeapRefSplittingTest { assertEquals(2, concreteRefs.size) assertSame(val2, concreteRefs[0].expr) assertSame(cond1 and !cond2, concreteRefs[0].guard) - // we need expr simplifier here, because mkAndNoFlatten produces too complicated expression + // we need expr simplifier here, because mkAnd with flat=false produces too complicated expression assertSame(val1, concreteRefs[1].expr) assertSame(!(cond1 and !cond2) and cond1 and cond2, KExprSimplifier(this).apply(concreteRefs[1].guard)) } @@ -209,7 +214,7 @@ class HeapRefSplittingTest { val res2 = heap.readField(ref2, valueFieldDescr.first, valueFieldDescr.second) assertIs>(res2) - assertEquals(res1.region, res2.region) + assertEquals(res1.collection, res2.collection) } @Test @@ -232,7 +237,7 @@ class HeapRefSplittingTest { val res2 = heap.readField(ref2, addressFieldDescr.first, addressFieldDescr.second) assertIs>(res2) - assertEquals(res1.region, res2.region) + assertEquals(res1.collection, res2.collection) val res3 = heap.readField(ref3, addressFieldDescr.first, addressFieldDescr.second) assertSame(val3, res3) diff --git a/usvm-core/src/test/kotlin/org/usvm/memory/MapCompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/memory/MapCompositionTest.kt index f80edb635..6742fe051 100644 --- a/usvm-core/src/test/kotlin/org/usvm/memory/MapCompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/memory/MapCompositionTest.kt @@ -1,12 +1,15 @@ package org.usvm.memory +import io.ksmt.expr.KExpr +import io.ksmt.utils.mkConst import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test -import io.ksmt.expr.KExpr -import io.ksmt.utils.mkConst +import org.usvm.TestKeyInfo import org.usvm.UAddressSort +import org.usvm.UBoolExpr import org.usvm.UBv32Sort import org.usvm.UComponents import org.usvm.UComposer @@ -16,61 +19,66 @@ import org.usvm.UExpr import org.usvm.UHeapRef import org.usvm.USizeExpr import org.usvm.USizeSort -import org.usvm.shouldNotBeCalled +import org.usvm.collection.array.USymbolicArrayAllocatedToAllocatedCopyAdapter +import org.usvm.collection.array.USymbolicArrayCopyAdapter +import org.usvm.collection.array.UAllocatedArrayId import org.usvm.util.SetRegion import org.usvm.util.emptyRegionTree +import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertNotSame import kotlin.test.assertNull import kotlin.test.assertSame +import kotlin.test.assertTrue -class MapCompositionTest { +class MapCompositionTest { private lateinit var ctx: UContext - private lateinit var composer: UComposer + private lateinit var composer: UComposer @BeforeEach fun initializeContext() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents = mockk() every { components.mkTypeSystem(any()) } returns mockk() ctx = UContext(components) composer = mockk() } @Test + @Disabled("Non clear") fun testWriteIntoEmptyTreeRegionAfterComposition() = with(ctx) { val concreteAddr = UConcreteHeapRef(this, address = 1) val symbolicAddr = addressSort.mkConst("addr") val value = bv32Sort.mkConst("value") - val updatesToCompose = UTreeUpdates, UBv32Sort>( - updates = emptyRegionTree(), - keyToRegion = { + val keyInfo = object : TestKeyInfo> { + override fun keyToRegion(key: UHeapRef): SetRegion { val singleRegion: SetRegion> = SetRegion.singleton(concreteAddr) - - if (it == symbolicAddr) { + return if (key == symbolicAddr) { // Z \ {1} SetRegion.universe().subtract(singleRegion) } else { // {1} singleRegion } - }, - keyRangeToRegion = { _, _ -> error("Should not be called") }, - symbolicEq = { _, _ -> error("Should not be called") }, - concreteCmp = { _, _ -> error("Should not be called") }, - symbolicCmp = { _, _ -> error("Should not be called") } + } + } + + val updatesToCompose = UTreeUpdates, UBv32Sort>( + updates = emptyRegionTree(), + keyInfo = keyInfo ).write(symbolicAddr, value, guard = trueExpr) - val composer = mockk>() + val composer = mockk>() every { composer.compose(symbolicAddr) } returns concreteAddr 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.filterMap(keyMapper = { composer.compose(it) }, composer, keyInfo) - assert(composedUpdates.isEmpty()) + // Why should updates be empty after composition? + assertTrue(composedUpdates.isEmpty()) } @Test @@ -84,41 +92,46 @@ class MapCompositionTest { val value = bv32Sort.mkConst("value") - val updatesToCompose = UTreeUpdates, UBv32Sort>( - updates = emptyRegionTree(), - keyToRegion = { - when (it) { + val keyInfo = object : TestKeyInfo> { + override fun keyToRegion(key: UHeapRef): SetRegion { + return when (key) { // Z \ {1, 2} symbolicAddr -> { SetRegion.universe().subtract(SetRegion.ofSet(fstConcreteAddr, sndConcreteAddr)) } // {1, 2, 3} thirdConcreteAddr -> SetRegion.ofSet(fstConcreteAddr, sndConcreteAddr, thirdConcreteAddr) - else -> SetRegion.singleton(it) + else -> SetRegion.singleton(key) } - }, - keyRangeToRegion = { _, _ -> shouldNotBeCalled() }, - symbolicEq = { _, _ -> shouldNotBeCalled() }, - concreteCmp = { _, _ -> shouldNotBeCalled() }, - symbolicCmp = { _, _ -> shouldNotBeCalled() } + } + } + + val updatesToCompose = UTreeUpdates, UBv32Sort>( + updates = emptyRegionTree(), + keyInfo ).write(symbolicAddr, value, guard = trueExpr) - val composer = mockk>() + val composer = mockk>() every { composer.compose(symbolicAddr) } returns thirdConcreteAddr every { composer.compose(value) } returns 1.toBv() 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.filterMap(keyMapper = { composer.compose(it) }, composer, keyInfo) assertFalse(composedUpdates.isEmpty()) // Write in the composedUpdates by a key with estimated region {3} // If we'd have an initial region for the third address, it'd contain an update by region {1, 2, 3} // Therefore, such writings cause updates splitting. Otherwise, it contains only one update. + val updatedKeyInfo = object : TestKeyInfo> { + override fun keyToRegion(key: UHeapRef): SetRegion = + SetRegion.singleton(thirdConcreteAddr) + } + val updatedByTheSameRegion = composedUpdates - .copy(keyToRegion = { SetRegion.singleton(thirdConcreteAddr) }) + .copy(keyInfo = updatedKeyInfo) .write(thirdConcreteAddr, 42.toBv(), guard = trueExpr) assertNotNull(updatedByTheSameRegion.singleOrNull()) @@ -131,13 +144,17 @@ class MapCompositionTest { val value = bv32Sort.mkConst("value") val guard = boolSort.mkConst("guard") - val updateNode = UPinpointUpdateNode(key, value, { k1, k2 -> k1 eq k2 }, guard) + val keyInfo = object : TestKeyInfo> { + override fun eqSymbolic(ctx: UContext, key1: UHeapRef, key2: UHeapRef): UBoolExpr = key1 eq key2 + } + + val updateNode = UPinpointUpdateNode(key, keyInfo, value, guard) every { composer.compose(key) } returns key 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, keyInfo) assertSame(expected = updateNode, actual = mappedNode) } @@ -148,7 +165,11 @@ class MapCompositionTest { val value = bv32Sort.mkConst("value") val guard = boolSort.mkConst("guard") - val updateNode = UPinpointUpdateNode(key, value, { k1, k2 -> k1 eq k2 }, guard) + val keyInfo = object : TestKeyInfo> { + override fun eqSymbolic(ctx: UContext, key1: UHeapRef, key2: UHeapRef): UBoolExpr = key1 eq key2 + } + + val updateNode = UPinpointUpdateNode(key, keyInfo, value, guard) val composedKey = addressSort.mkConst("interpretedKey") @@ -156,45 +177,45 @@ 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, keyInfo) assertNotSame(illegal = updateNode, actual = mappedNode) - assertSame(expected = composedKey, actual = mappedNode.key) - assertSame(expected = 1.toBv(), actual = mappedNode.value) - assertSame(expected = mkTrue(), actual = mappedNode.guard) + assertSame(expected = composedKey, actual = mappedNode?.key) + assertSame(expected = 1.toBv(), actual = mappedNode?.value) + assertSame(expected = mkTrue(), actual = mappedNode?.guard) } @Test fun testRangeUpdateNodeWithoutCompositionEffect() = with(ctx) { - 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 guard = boolSort.mkConst("guard") + val keyInfo = object : TestKeyInfo> { + } + + val region = UAllocatedArrayId(Unit, bv32Sort, address = 1).emptyRegion() val updateNode = URangedUpdateNode( - fromKey, - toKey, - region = region, - concreteComparer = { _, _ -> shouldNotBeCalled() }, - symbolicComparer = { _, _ -> shouldNotBeCalled() }, - keyConverter = UAllocatedToAllocatedKeyConverter( - srcSymbolicArrayIndex = addr to fromKey, - dstFromSymbolicArrayIndex = addr to fromKey, - dstToIndex = toKey - ), + region, + USymbolicArrayAllocatedToAllocatedCopyAdapter(fromKey, fromKey, toKey, keyInfo), guard ) - every { composer.compose(addr) } returns addr every { composer.compose(fromKey) } returns fromKey + every { composer.apply(fromKey) } returns fromKey + every { composer.compose(toKey) } returns toKey - every { region.map(composer) } returns region + every { composer.apply(toKey) } returns toKey + every { composer.compose(guard) } returns guard - val mappedUpdateNode = updateNode.map({ k -> composer.compose((k)) }, composer) + every { composer.compose(mkBv(0)) } returns mkBv(0) + every { composer.memory } returns UMemory(ctx, mockk()) + + val mappedUpdateNode = updateNode.map({ k -> composer.compose((k)) }, composer, keyInfo) - assertSame(expected = updateNode, actual = mappedUpdateNode) + // Region.contextMemory changed after composition + assertEquals(expected = updateNode, actual = mappedUpdateNode) } @Test @@ -202,52 +223,59 @@ class MapCompositionTest { val addr = mkConcreteHeapRef(0) val fromKey = sizeSort.mkConst("fromKey") val toKey = sizeSort.mkConst("toKey") - val region = mockk, USizeExpr, UBv32Sort>>() val guard = boolSort.mkConst("guard") + val keyInfo = object : TestKeyInfo> { + } + + val region = UAllocatedArrayId(Unit, bv32Sort, addr.address).emptyRegion() val updateNode = URangedUpdateNode( - fromKey, - toKey, - region = region, - concreteComparer = { _, _ -> shouldNotBeCalled() }, - symbolicComparer = { _, _ -> shouldNotBeCalled() }, - keyConverter = UAllocatedToAllocatedKeyConverter( - srcSymbolicArrayIndex = addr to fromKey, - dstFromSymbolicArrayIndex = addr to fromKey, - dstToIndex = toKey - ), + region, + USymbolicArrayAllocatedToAllocatedCopyAdapter(fromKey, fromKey, toKey, keyInfo), guard ) val composedFromKey = sizeSort.mkConst("composedFromKey") val composedToKey = sizeSort.mkConst("composedToKey") - val composedRegion = mockk, UExpr, UBv32Sort>>() val composedGuard = mkTrue() every { composer.compose(addr) } returns addr + every { composer.compose(fromKey) } returns composedFromKey + every { composer.apply(fromKey) } returns composedFromKey + every { composer.compose(toKey) } returns composedToKey - every { region.map(composer) } returns composedRegion + every { composer.apply(toKey) } returns composedToKey + every { composer.compose(guard) } returns composedGuard + every { composer.compose(mkBv(0)) } returns mkBv(0) + every { composer.memory } returns UMemory(ctx, mockk()) - val mappedUpdateNode = updateNode.map({ k -> composer.compose((k)) }, composer) + val mappedUpdateNode = updateNode.map({ k -> composer.compose((k)) }, composer, keyInfo) assertNotSame(illegal = updateNode, actual = mappedUpdateNode) - assertSame(expected = composedFromKey, actual = mappedUpdateNode.fromKey) - assertSame(expected = composedToKey, actual = mappedUpdateNode.toKey) - assertSame(expected = composedRegion, actual = mappedUpdateNode.region) - assertSame(expected = composedGuard, actual = mappedUpdateNode.guard) + assertSame( + expected = composedFromKey, + actual = (mappedUpdateNode?.adapter as? USymbolicArrayCopyAdapter<*, *>)?.dstFrom + ) + assertSame( + expected = composedToKey, + actual = (mappedUpdateNode?.adapter as? USymbolicArrayCopyAdapter<*, *>)?.dstTo + ) + assertSame(expected = composedGuard, actual = mappedUpdateNode?.guard) + + // Region.contextMemory changed after composition + assertEquals(expected = region, actual = mappedUpdateNode?.sourceCollection) } @Test fun testEmptyUpdatesMapOperation() { - val emptyUpdates = UFlatUpdates, UBv32Sort>( - { _, _ -> shouldNotBeCalled() }, - { _, _ -> shouldNotBeCalled() }, - { _, _ -> shouldNotBeCalled() } - ) + val keyInfo = object : TestKeyInfo> { + } + + val emptyUpdates = UFlatUpdates, UBv32Sort>(keyInfo) - val mappedUpdates = emptyUpdates.map({ k -> composer.compose(k) }, composer) + val mappedUpdates = emptyUpdates.filterMap({ k -> composer.compose(k) }, composer, keyInfo) assertSame(expected = emptyUpdates, actual = mappedUpdates) } @@ -259,11 +287,11 @@ class MapCompositionTest { val sndKey = addressSort.mkConst("sndKey") val sndValue = bv32Sort.mkConst("sndValue") - val flatUpdates = UFlatUpdates, UBv32Sort>( - { _, _ -> shouldNotBeCalled() }, - { _, _ -> shouldNotBeCalled() }, - { _, _ -> shouldNotBeCalled() } - ).write(fstKey, fstValue, guard = trueExpr) + val keyInfo = object : TestKeyInfo> { + } + + val flatUpdates = UFlatUpdates, UBv32Sort>(keyInfo) + .write(fstKey, fstValue, guard = trueExpr) .write(sndKey, sndValue, guard = trueExpr) every { composer.compose(fstKey) } returns fstKey @@ -272,7 +300,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.filterMap({ k -> composer.compose(k) }, composer, keyInfo) assertSame(expected = flatUpdates, actual = mappedUpdates) } @@ -284,11 +312,11 @@ class MapCompositionTest { val sndKey = addressSort.mkConst("sndKey") val sndValue = bv32Sort.mkConst("sndValue") - val flatUpdates = UFlatUpdates, UBv32Sort>( - { _, _ -> shouldNotBeCalled() }, - { _, _ -> shouldNotBeCalled() }, - { _, _ -> shouldNotBeCalled() } - ).write(fstKey, fstValue, guard = trueExpr) + val keyInfo = object : TestKeyInfo> { + } + + val flatUpdates = UFlatUpdates, UBv32Sort>(keyInfo) + .write(fstKey, fstValue, guard = trueExpr) .write(sndKey, sndValue, guard = trueExpr) val composedFstKey = addressSort.mkConst("composedFstKey") @@ -302,7 +330,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.filterMap({ k -> composer.compose(k) }, composer, keyInfo) assertNotSame(illegal = flatUpdates, actual = mappedUpdates) @@ -324,13 +352,15 @@ class MapCompositionTest { val sndKey = addressSort.mkConst("sndKey") val sndValue = bv32Sort.mkConst("sndValue") + val keyInfo = object : TestKeyInfo> { + override fun keyToRegion(key: UHeapRef): SetRegion = SetRegion.singleton(key) + override fun keyRangeRegion(from: UHeapRef, to: UHeapRef): SetRegion = + SetRegion.ofSet(from, to) + } + val treeUpdates = UTreeUpdates, SetRegion>, UBv32Sort>( emptyRegionTree(), - { k -> SetRegion.singleton(k) }, - { k1, k2 -> SetRegion.ofSet(k1, k2) }, - { _, _ -> shouldNotBeCalled() }, - { _, _ -> shouldNotBeCalled() }, - { _, _ -> shouldNotBeCalled() } + keyInfo ).write(fstKey, fstValue, guard = trueExpr) .write(sndKey, sndValue, guard = trueExpr) @@ -340,7 +370,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.filterMap({ k -> composer.compose(k) }, composer, keyInfo) assertSame(expected = treeUpdates, actual = mappedUpdates) } @@ -352,13 +382,14 @@ class MapCompositionTest { val sndKey = addressSort.mkConst("sndKey") val sndValue = bv32Sort.mkConst("sndValue") + val keyInfo = object : TestKeyInfo> { + override fun keyToRegion(key: UHeapRef): SetRegion = SetRegion.universe() + override fun keyRangeRegion(from: UHeapRef, to: UHeapRef): SetRegion = SetRegion.universe() + } + val treeUpdates = UTreeUpdates, SetRegion>, UBv32Sort>( emptyRegionTree(), - { SetRegion.universe() }, - { _, _ -> SetRegion.universe() }, - { _, _ -> shouldNotBeCalled() }, - { _, _ -> shouldNotBeCalled() }, - { _, _ -> shouldNotBeCalled() } + keyInfo ).write(fstKey, fstValue, guard = trueExpr) .write(sndKey, sndValue, guard = trueExpr) @@ -373,7 +404,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.filterMap({ k -> composer.compose(k) }, composer, keyInfo) assertNotSame(illegal = treeUpdates, actual = mappedUpdates) diff --git a/usvm-core/src/test/kotlin/org/usvm/memory/MemoryRegionTests.kt b/usvm-core/src/test/kotlin/org/usvm/memory/MemoryRegionTests.kt index 0972dd3f1..53811eb06 100644 --- a/usvm-core/src/test/kotlin/org/usvm/memory/MemoryRegionTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/memory/MemoryRegionTests.kt @@ -6,12 +6,13 @@ import io.mockk.mockk import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.usvm.TestKeyInfo import org.usvm.Type import org.usvm.UBv32Sort import org.usvm.UComponents import org.usvm.UContext import org.usvm.UHeapRef -import org.usvm.shouldNotBeCalled +import org.usvm.collection.array.UAllocatedArrayId import org.usvm.util.SetRegion import org.usvm.util.emptyRegionTree import kotlin.test.assertNotNull @@ -22,7 +23,7 @@ class MemoryRegionTests { @BeforeEach fun initializeContext() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents = mockk() every { components.mkTypeSystem(any()) } returns mockk() ctx = UContext(components) } @@ -38,13 +39,13 @@ class MemoryRegionTests { with(ctx) { val address = mkConcreteHeapRef(address = 1) + val keyInfo = object : TestKeyInfo> { + override fun keyToRegion(key: UHeapRef): SetRegion = SetRegion.universe() + } + val treeUpdates = UTreeUpdates, UBv32Sort>( updates = emptyRegionTree(), - keyToRegion = { SetRegion.universe() }, - keyRangeToRegion = { _, _ -> shouldNotBeCalled() }, - symbolicEq = { _, _ -> shouldNotBeCalled() }, - concreteCmp = { _, _ -> shouldNotBeCalled() }, - symbolicCmp = { _, _ -> shouldNotBeCalled() } + keyInfo ).write(address, 1.toBv(), mkTrue()) .write(address, 2.toBv(), mkTrue()) .write(address, 3.toBv(), mkTrue()) @@ -63,13 +64,13 @@ class MemoryRegionTests { val guard = boolSort.mkConst("boolConst") val anotherGuard = boolSort.mkConst("anotherBoolConst") + val keyInfo = object : TestKeyInfo> { + override fun keyToRegion(key: UHeapRef): SetRegion = SetRegion.universe() + } + val treeUpdates = UTreeUpdates, UBv32Sort>( updates = emptyRegionTree(), - keyToRegion = { SetRegion.universe() }, - keyRangeToRegion = { _, _ -> shouldNotBeCalled() }, - symbolicEq = { _, _ -> shouldNotBeCalled() }, - concreteCmp = { _, _ -> shouldNotBeCalled() }, - symbolicCmp = { _, _ -> shouldNotBeCalled() } + keyInfo ).write(address, 1.toBv(), guard) .write(address, 2.toBv(), anotherGuard) @@ -88,7 +89,8 @@ class MemoryRegionTests { val idx1 = mkRegisterReading(0, sizeSort) val idx2 = mkRegisterReading(1, sizeSort) - val memoryRegion = emptyAllocatedArrayRegion(mockk(), 0, sizeSort) + val memoryRegion = UAllocatedArrayId(mockk(), sizeSort, 0) + .emptyRegion() .write(idx1, mkBv(0), trueExpr) .write(idx2, mkBv(1), trueExpr) diff --git a/usvm-core/src/test/kotlin/org/usvm/memory/UpdatesIteratorTest.kt b/usvm-core/src/test/kotlin/org/usvm/memory/UpdatesIteratorTest.kt index 3c2f44b7f..a281ec045 100644 --- a/usvm-core/src/test/kotlin/org/usvm/memory/UpdatesIteratorTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/memory/UpdatesIteratorTest.kt @@ -5,6 +5,8 @@ import io.mockk.mockk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.usvm.TestKeyInfo +import org.usvm.UBoolExpr import org.usvm.UBv32Sort import org.usvm.UComponents import org.usvm.UContext @@ -17,7 +19,7 @@ class UpdatesIteratorTest { private lateinit var ctx: UContext @BeforeEach fun initializeContext() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents<*> = mockk() every { components.mkTypeSystem(any()) } returns mockk() ctx = UContext(components) } @@ -25,19 +27,23 @@ class UpdatesIteratorTest { @Test fun testTreeRegionUpdates() { with(ctx) { - val treeUpdates = UTreeUpdates, UBv32Sort>( - emptyRegionTree(), - { key -> + + val keyInfo = object : TestKeyInfo> { + override fun keyToRegion(key: Int): SetRegion = if (key != 10) { SetRegion.singleton(key) } else { SetRegion.ofSet(1, 2, 3) } - }, - { _, _ -> throw UnsupportedOperationException() }, - { k1, k2 -> mkEq(k1.toBv(), k2.toBv()) }, - { k1, k2 -> k1 == k2 }, - { _, _ -> throw UnsupportedOperationException() } + + override fun eqConcrete(key1: Int, key2: Int): Boolean = key1 == key2 + override fun eqSymbolic(ctx: UContext, key1: Int, key2: Int): UBoolExpr = + ctx.mkEq(key1.toBv(), key2.toBv()) + } + + val treeUpdates = UTreeUpdates, UBv32Sort>( + emptyRegionTree(), + keyInfo ).write(10, 10.toBv(), guard = mkTrue()) .write(1, 1.toBv(), guard = mkTrue()) .write(2, 2.toBv(), guard = mkTrue()) @@ -50,11 +56,9 @@ class UpdatesIteratorTest { @Test fun testFlatUpdatesIterator() = with(ctx) { - val flatUpdates = UFlatUpdates( - { _, _ -> throw NotImplementedError() }, - { _, _ -> throw NotImplementedError() }, - { _, _ -> throw NotImplementedError() } - ).write(key = 10, value = 10.toBv(), guard = mkTrue()) + val keyInfo = object : TestKeyInfo> {} + val flatUpdates = UFlatUpdates(keyInfo) + .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()) diff --git a/usvm-core/src/test/kotlin/org/usvm/model/ModelCompositionTest.kt b/usvm-core/src/test/kotlin/org/usvm/model/ModelCompositionTest.kt index 75da4afd0..2370d36f2 100644 --- a/usvm-core/src/test/kotlin/org/usvm/model/ModelCompositionTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/model/ModelCompositionTest.kt @@ -9,16 +9,23 @@ import org.junit.jupiter.api.Test import org.usvm.Field import org.usvm.NULL_ADDRESS import org.usvm.Type -import org.usvm.UBv32Sort import org.usvm.UComponents import org.usvm.UComposer import org.usvm.UConcreteHeapRef import org.usvm.UContext -import org.usvm.memory.UInputToAllocatedKeyConverter -import org.usvm.memory.emptyAllocatedArrayRegion -import org.usvm.memory.emptyInputArrayLengthRegion -import org.usvm.memory.emptyInputArrayRegion -import org.usvm.memory.emptyInputFieldRegion +import org.usvm.UHeapRef +import org.usvm.collection.array.USymbolicArrayInputToAllocatedCopyAdapter +import org.usvm.collection.array.UAllocatedArrayId +import org.usvm.collection.array.UInputArrayId +import org.usvm.collection.array.length.UInputArrayLengthId +import org.usvm.collection.field.UInputFieldId +import org.usvm.memory.key.USizeExprKeyInfo +import org.usvm.collection.array.length.UArrayLengthsRegionId +import org.usvm.collection.array.UArrayRegionId +import org.usvm.collection.field.UFieldsRegionId +import org.usvm.collection.array.UArrayEagerModelRegion +import org.usvm.collection.array.length.UArrayLengthEagerModelRegion +import org.usvm.collection.field.UFieldsEagerModelRegion import org.usvm.sampleUValue import kotlin.test.assertSame @@ -28,7 +35,7 @@ class ModelCompositionTest { @BeforeEach fun initializeContext() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents<*> = mockk() every { components.mkTypeSystem(any()) } returns mockk() ctx = UContext(components) concreteNull = ctx.mkConcreteHeapRef(NULL_ADDRESS) @@ -36,18 +43,16 @@ class ModelCompositionTest { @Test fun testComposeAllocatedArray() = with(ctx) { - val heapEvaluator = UHeapEagerModel( + val stackModel = URegistersStackEagerModel( concreteNull, - mapOf(), - mapOf(), - mapOf(), + 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()) + val model = UModelBase(ctx, stackModel, mockk(), mockk(), emptyMap(), concreteNull) + val composer = UComposer(this, model) - val region = emptyAllocatedArrayRegion(mockk(), 1, bv32Sort) + val region = UAllocatedArrayId(mockk(), bv32Sort, 1) + .emptyRegion() .write(0.toBv(), 0.toBv(), trueExpr) .write(1.toBv(), 1.toBv(), trueExpr) .write(mkRegisterReading(1, sizeSort), 2.toBv(), trueExpr) @@ -61,28 +66,39 @@ class ModelCompositionTest { @Test fun testComposeRangedUpdate() = with(ctx) { val arrayType = mockk() + val arrayMemoryId = UArrayRegionId(arrayType, bv32Sort) + val composedSymbolicHeapRef = ctx.mkConcreteHeapRef(-1) - val inputArray = UMemory2DArray(persistentMapOf((composedSymbolicHeapRef to mkBv(0)) to mkBv(1)), mkBv(0)) - val heapEvaluator = UHeapEagerModel( - concreteNull, - mapOf(), - mapOf(arrayType to inputArray), - mapOf(), + val inputArray = UMemory2DArray( + persistentMapOf((composedSymbolicHeapRef to mkBv(0)) to mkBv(1)), mkBv(0) ) + val arrayModel = UArrayEagerModelRegion(arrayMemoryId, inputArray) - val stackModel = - URegistersStackEagerModel(concreteNull, mapOf(0 to composedSymbolicHeapRef, 1 to mkBv(0))) - val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + val stackModel = URegistersStackEagerModel( + concreteNull, mapOf(0 to composedSymbolicHeapRef, 1 to mkBv(0)) + ) - val symbolicRef = mkRegisterReading(0, addressSort) + val model = UModelBase( + ctx, stackModel, mockk(), mockk(), mapOf(arrayMemoryId to arrayModel), concreteNull + ) + val composer = UComposer(this, model) - val fromRegion = emptyInputArrayRegion(arrayType, bv32Sort) + val symbolicRef = mkRegisterReading(0, addressSort) as UHeapRef + + val fromRegion = UInputArrayId(arrayType, bv32Sort).emptyRegion() 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 adapter = USymbolicArrayInputToAllocatedCopyAdapter( + symbolicRef to mkSizeExpr(0), + mkSizeExpr(0), + mkSizeExpr(5), + USizeExprKeyInfo + ) + + val concreteRegion = UAllocatedArrayId(arrayType, bv32Sort, concreteRef.address) + .emptyRegion() + .copyRange(fromRegion, adapter, trueExpr) val idx = mkRegisterReading(1, sizeSort) @@ -105,13 +121,10 @@ class ModelCompositionTest { val composedRef3 = mkConcreteHeapRef(-4) val arrayType = mockk() + + val arrayLengthMemoryId = UArrayLengthsRegionId(sizeSort, arrayType) val inputLength = UMemory1DArray(persistentMapOf(composedRef0 to mkBv(42)), mkBv(0)) - val heapEvaluator = UHeapEagerModel( - concreteNull, - mapOf(), - mapOf(), - mapOf(arrayType to inputLength), - ) + val arrayLengthModel = UArrayLengthEagerModelRegion(arrayLengthMemoryId, inputLength) val stackModel = URegistersStackEagerModel( concreteNull, @@ -123,9 +136,14 @@ class ModelCompositionTest { ) ) - val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + val model = UModelBase( + ctx, stackModel, mockk(), mockk(), mapOf(arrayLengthMemoryId to arrayLengthModel), concreteNull + ) + + val composer = UComposer(this, model) - val region = emptyInputArrayLengthRegion(arrayType, bv32Sort) + val region = UInputArrayLengthId(arrayType, bv32Sort) + .emptyRegion() .write(symbolicRef1, 0.toBv(), trueExpr) .write(symbolicRef2, 1.toBv(), trueExpr) .write(symbolicRef3, 2.toBv(), trueExpr) @@ -148,13 +166,10 @@ class ModelCompositionTest { val composedRef3 = mkConcreteHeapRef(-4) val field = mockk() + val fieldMemoryId = UFieldsRegionId(field, addressSort) + val inputField = UMemory1DArray(persistentMapOf(composedRef0 to composedRef0), concreteNull) - val heapEvaluator = UHeapEagerModel( - concreteNull, - mapOf(field to inputField), - mapOf(), - mapOf(), - ) + val fieldModel = UFieldsEagerModelRegion(fieldMemoryId, inputField) val stackModel = URegistersStackEagerModel( concreteNull, @@ -166,9 +181,14 @@ class ModelCompositionTest { ) ) - val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + val model = UModelBase( + ctx, stackModel, mockk(), mockk(), mapOf(fieldMemoryId to fieldModel), concreteNull + ) + + val composer = UComposer(this, model) - val region = emptyInputFieldRegion(field, addressSort) + val region = UInputFieldId(field, addressSort) + .emptyRegion() .write(symbolicRef1, symbolicRef1, trueExpr) .write(symbolicRef2, symbolicRef2, trueExpr) .write(symbolicRef3, symbolicRef3, trueExpr) @@ -180,13 +200,6 @@ class ModelCompositionTest { @Test fun testComposeAllocatedArrayWithFalseOverwrite() = with(ctx) { - val heapEvaluator = UHeapEagerModel( - concreteNull, - mapOf(), - mapOf(), - mapOf(), - ) - val index0 = 0.toBv() val index1 = 1.toBv() @@ -198,9 +211,13 @@ class ModelCompositionTest { val trueGuard = mkRegisterReading(0, boolSort) val falseGuard = mkRegisterReading(1, boolSort) - val composer = UComposer(this, stackModel, heapEvaluator, mockk(), mockk()) + val model = UModelBase( + ctx, stackModel, mockk(), mockk(), emptyMap(), concreteNull + ) + + val composer = UComposer(this, model) - val emptyRegion = emptyAllocatedArrayRegion(mockk(), 1, bv32Sort) + val emptyRegion = UAllocatedArrayId(mockk(), bv32Sort, 1).emptyRegion() run { val region = emptyRegion @@ -222,4 +239,4 @@ class ModelCompositionTest { assertEquals(defaultValue, expr) } } -} \ No newline at end of file +} diff --git a/usvm-core/src/test/kotlin/org/usvm/model/ModelDecodingTest.kt b/usvm-core/src/test/kotlin/org/usvm/model/ModelDecodingTest.kt index 63b427303..d3ba084ce 100644 --- a/usvm-core/src/test/kotlin/org/usvm/model/ModelDecodingTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/model/ModelDecodingTest.kt @@ -9,15 +9,20 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.usvm.Field import org.usvm.Method -import org.usvm.UArrayIndexLValue import org.usvm.UComponents import org.usvm.UConcreteHeapRef import org.usvm.UContext import org.usvm.UIndexedMocker -import org.usvm.URegisterLValue +import org.usvm.api.allocate +import org.usvm.api.readArrayIndex +import org.usvm.api.readField +import org.usvm.api.writeArrayIndex +import org.usvm.api.writeField import org.usvm.constraints.UPathConstraints -import org.usvm.memory.URegionHeap +import org.usvm.memory.UMemory +import org.usvm.memory.URegisterStackLValue import org.usvm.memory.URegistersStack +import org.usvm.collection.array.UArrayIndexLValue import org.usvm.solver.USatResult import org.usvm.solver.USoftConstraintsProvider import org.usvm.solver.USolverBase @@ -30,36 +35,36 @@ private typealias Type = SingleTypeSystem.SingleType class ModelDecodingTest { private lateinit var ctx: UContext - private lateinit var solver: USolverBase + private lateinit var solver: USolverBase private lateinit var pc: UPathConstraints private lateinit var stack: URegistersStack - private lateinit var heap: URegionHeap + private lateinit var heap: UMemory private lateinit var mocker: UIndexedMocker @BeforeEach fun initializeContext() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents = mockk() every { components.mkTypeSystem(any()) } returns SingleTypeSystem ctx = UContext(components) - val softConstraintsProvider = USoftConstraintsProvider(ctx) - val (translator, decoder) = buildTranslatorAndLazyDecoder(ctx) + val softConstraintsProvider = USoftConstraintsProvider(ctx) + val (translator, decoder) = buildTranslatorAndLazyDecoder(ctx) val typeSolver = UTypeSolver(SingleTypeSystem) solver = USolverBase(ctx, KZ3Solver(ctx), typeSolver, translator, decoder, softConstraintsProvider) + pc = UPathConstraints(ctx) + stack = URegistersStack() stack.push(10) - heap = URegionHeap(ctx) mocker = UIndexedMocker(ctx) - - pc = UPathConstraints(ctx) + heap = UMemory(ctx, pc.typeConstraints, stack, mocker) } @Test fun testSmoke(): Unit = with(ctx) { val status = solver.checkWithSoftConstraints(pc) - assertIs>>(status) + assertIs>>(status) } @Test @@ -78,7 +83,7 @@ class ModelDecodingTest { pc += mkHeapRefEq(symbolicRef0, nullRef).not() val status = solver.checkWithSoftConstraints(pc) - val model = assertIs>>(status).model + val model = assertIs>>(status).model val expr = heap.readField(symbolicRef1, field, bv32Sort) assertSame(mkBv(42), model.eval(expr)) @@ -100,7 +105,7 @@ class ModelDecodingTest { pc += heap.readField(symbolicRef0, field, addressSort) eq symbolicRef1 val status = solver.checkWithSoftConstraints(pc) - val model = assertIs>>(status).model + val model = assertIs>>(status).model val expr = heap.readField(symbolicRef1, field, addressSort) @@ -122,7 +127,7 @@ class ModelDecodingTest { pc += ref1 neq nullRef val status = solver.checkWithSoftConstraints(pc) - val model = assertIs>>(status).model + val model = assertIs>>(status).model val mockedValueEqualsRef1 = mockedValue eq ref1 @@ -144,7 +149,7 @@ class ModelDecodingTest { pc += ref1 neq nullRef val status = solver.checkWithSoftConstraints(pc) - assertIs>>(status) + assertIs>>(status) } @Test @@ -171,7 +176,7 @@ class ModelDecodingTest { pc += (symbolicRef2 neq nullRef) and (readRef1 neq readRef2) val status = solver.checkWithSoftConstraints(pc) - val model = assertIs>>(status).model + val model = assertIs>>(status).model assertSame(model.eval(symbolicRef1), model.eval(symbolicRef2)) } @@ -198,7 +203,7 @@ class ModelDecodingTest { pc += symbolicRef0 eq symbolicRef1 val status = solver.checkWithSoftConstraints(pc) - val model = assertIs>>(status).model + val model = assertIs>>(status).model assertSame(falseExpr, model.eval(symbolicRef2 eq symbolicRef0)) assertSame(model.eval(readRef), model.eval(symbolicRef2)) @@ -217,9 +222,9 @@ class ModelDecodingTest { pc += readExpr eq mkBv(42) val status = solver.checkWithSoftConstraints(pc) - val model = assertIs>>(status).model + val model = assertIs>>(status).model - val ref = assertIs(model.read(URegisterLValue(addressSort, 0))) + val ref = assertIs(model.read(URegisterStackLValue(addressSort, 0))) val expr = model.read(UArrayIndexLValue(bv32Sort, ref, concreteIdx, array)) assertEquals(mkBv(42), expr) } diff --git a/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt b/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt index 4037ea647..c4b5f2d76 100644 --- a/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt +++ b/usvm-core/src/test/kotlin/org/usvm/ps/RandomTreePathSelectorTests.kt @@ -173,7 +173,7 @@ internal class RandomTreePathSelectorTests { } companion object { - private fun , Statement> registerLocationsInTree( + private fun , Statement> registerLocationsInTree( root: PathsTrieNode, selector: RandomTreePathSelector, ) { diff --git a/usvm-core/src/test/kotlin/org/usvm/solver/SoftConstraintsTest.kt b/usvm-core/src/test/kotlin/org/usvm/solver/SoftConstraintsTest.kt index a5beba162..3c095f6ef 100644 --- a/usvm-core/src/test/kotlin/org/usvm/solver/SoftConstraintsTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/solver/SoftConstraintsTest.kt @@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test import org.usvm.UComponents import org.usvm.UContext import org.usvm.constraints.UPathConstraints -import org.usvm.memory.emptyInputArrayLengthRegion +import org.usvm.collection.array.length.UInputArrayLengthId import org.usvm.model.ULazyModelDecoder import org.usvm.model.buildTranslatorAndLazyDecoder import org.usvm.types.single.SingleTypeSystem @@ -19,22 +19,22 @@ import kotlin.test.assertSame private typealias Type = SingleTypeSystem.SingleType -open class SoftConstraintsTest { +open class SoftConstraintsTest { private lateinit var ctx: UContext - private lateinit var softConstraintsProvider: USoftConstraintsProvider - private lateinit var translator: UExprTranslator - private lateinit var decoder: ULazyModelDecoder - private lateinit var solver: USolverBase + private lateinit var softConstraintsProvider: USoftConstraintsProvider + private lateinit var translator: UExprTranslator + private lateinit var decoder: ULazyModelDecoder + private lateinit var solver: USolverBase @BeforeEach fun initialize() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents = mockk() every { components.mkTypeSystem(any()) } returns SingleTypeSystem ctx = UContext(components) softConstraintsProvider = USoftConstraintsProvider(ctx) - val translatorWithDecoder = buildTranslatorAndLazyDecoder(ctx) + val translatorWithDecoder = buildTranslatorAndLazyDecoder(ctx) translator = translatorWithDecoder.first decoder = translatorWithDecoder.second @@ -71,7 +71,7 @@ open class SoftConstraintsTest { val sndExpr = mkBvSignedLessOrEqualExpr(sndRegister, thirdRegister) val sameAsFirstExpr = mkBvSignedLessOrEqualExpr(fstRegister, sndRegister) - val softConstraintsProvider = mockk>() + val softConstraintsProvider = mockk>() every { softConstraintsProvider.provide(any()) } answers { callOriginal() } @@ -114,7 +114,8 @@ open class SoftConstraintsTest { val arrayType = IntArray::class val inputRef = mkRegisterReading(0, addressSort) val secondInputRef = mkRegisterReading(1, addressSort) - val region = emptyInputArrayLengthRegion(arrayType, sizeSort) + val region = UInputArrayLengthId(arrayType, sizeSort) + .emptyRegion() .write(inputRef, mkRegisterReading(3, sizeSort), guard = trueExpr) val size = 25 @@ -138,7 +139,8 @@ open class SoftConstraintsTest { fun testUnsatCore() = with(ctx) { val arrayType = IntArray::class val inputRef = mkRegisterReading(0, addressSort) - val region = emptyInputArrayLengthRegion(arrayType, sizeSort) + val region = UInputArrayLengthId(arrayType, sizeSort) + .emptyRegion() .write(inputRef, mkRegisterReading(3, sizeSort), guard = trueExpr) val pc = UPathConstraints(ctx) diff --git a/usvm-core/src/test/kotlin/org/usvm/solver/TranslationTest.kt b/usvm-core/src/test/kotlin/org/usvm/solver/TranslationTest.kt index 5de9e88f6..8c042d525 100644 --- a/usvm-core/src/test/kotlin/org/usvm/solver/TranslationTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/solver/TranslationTest.kt @@ -16,30 +16,39 @@ import org.usvm.UAddressSort import org.usvm.UBv32Sort import org.usvm.UComponents import org.usvm.UContext -import org.usvm.memory.UAllocatedToAllocatedKeyConverter -import org.usvm.memory.UInputToAllocatedKeyConverter -import org.usvm.memory.UInputToInputKeyConverter -import org.usvm.memory.URegionHeap -import org.usvm.memory.emptyAllocatedArrayRegion -import org.usvm.memory.emptyInputArrayLengthRegion -import org.usvm.memory.emptyInputArrayRegion -import org.usvm.memory.emptyInputFieldRegion +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.api.allocate +import org.usvm.api.readArrayIndex +import org.usvm.api.writeArrayIndex +import org.usvm.collection.array.UAllocatedArrayId +import org.usvm.collection.array.UInputArrayId +import org.usvm.collection.array.USymbolicArrayAllocatedToAllocatedCopyAdapter +import org.usvm.collection.array.USymbolicArrayIndexKeyInfo +import org.usvm.collection.array.USymbolicArrayInputToAllocatedCopyAdapter +import org.usvm.collection.array.USymbolicArrayInputToInputCopyAdapter +import org.usvm.collection.array.length.UInputArrayLengthId +import org.usvm.collection.field.UInputFieldId +import org.usvm.collection.map.ref.URefMapEntryLValue +import org.usvm.memory.UMemory +import org.usvm.memory.key.USizeExprKeyInfo import kotlin.test.assertEquals import kotlin.test.assertSame class TranslationTest { private lateinit var ctx: RecordingCtx - private lateinit var heap: URegionHeap - private lateinit var translator: UExprTranslator + private lateinit var heap: UMemory + private lateinit var translator: UExprTranslator private lateinit var valueFieldDescr: Pair private lateinit var addressFieldDescr: Pair private lateinit var valueArrayDescr: Type private lateinit var addressArrayDescr: Type - class RecordingCtx(components: UComponents<*, *, *>) : UContext(components) { + class RecordingCtx(components: UComponents) : UContext(components) { var storeCallCounter = 0 private set + override fun mkArrayStore( array: KExpr>, index: KExpr, @@ -52,11 +61,11 @@ class TranslationTest { @BeforeEach fun initializeContext() { - val components: UComponents<*, *, *> = mockk() + val components: UComponents = mockk() every { components.mkTypeSystem(any()) } returns mockk() ctx = RecordingCtx(components) - heap = URegionHeap(ctx) + heap = UMemory(ctx, mockk()) translator = UExprTranslator(ctx) valueFieldDescr = mockk() to ctx.bv32Sort @@ -128,7 +137,8 @@ class TranslationTest { val val2 = mkBv(2) - val region = emptyInputArrayRegion(valueArrayDescr, bv32Sort) + val region = UInputArrayId(valueArrayDescr, bv32Sort) + .emptyRegion() .write(ref1 to idx1, val1, trueExpr) .write(ref2 to idx2, val2, trueExpr) @@ -140,7 +150,7 @@ class TranslationTest { val translated = translator.translate(reading) val expected = mkArraySort(addressSort, sizeSort, bv32Sort) - .mkConst(region.regionId.toString()) + .mkConst(region.collectionId.toString()) .store(translator.translate(ref1), translator.translate(idx1), val1) .store(translator.translate(ref2), translator.translate(idx2), val2) .select(translator.translate(ref3), translator.translate(idx3)) @@ -150,7 +160,7 @@ class TranslationTest { @Test fun testTranslateInputToAllocatedArrayCopy() = with(ctx) { - val ref1 = mkRegisterReading(0, addressSort) + val ref1 = mkRegisterReading(0, addressSort) as UHeapRef val idx1 = mkRegisterReading(1, sizeSort) val val1 = mkBv(1) @@ -158,22 +168,30 @@ class TranslationTest { val idx2 = mkRegisterReading(3, sizeSort) val val2 = mkBv(2) - val region = emptyInputArrayRegion(valueArrayDescr, bv32Sort) + val region = UInputArrayId(valueArrayDescr, bv32Sort) + .emptyRegion() .write(ref1 to idx1, val1, trueExpr) .write(ref2 to idx2, val2, trueExpr) val concreteRef = heap.allocate() - val keyConverter = UInputToAllocatedKeyConverter(ref1 to mkBv(0), concreteRef to mkBv(0), mkBv(5)) - val concreteRegion = emptyAllocatedArrayRegion(valueArrayDescr, concreteRef.address, bv32Sort) - .copyRange(region, mkBv(0), mkBv(5), keyConverter, trueExpr) + val adapter = USymbolicArrayInputToAllocatedCopyAdapter( + ref1 to mkSizeExpr(0), + mkSizeExpr(0), + mkSizeExpr(5), + USizeExprKeyInfo + ) + + val concreteRegion = UAllocatedArrayId(valueArrayDescr, bv32Sort, concreteRef.address) + .emptyRegion() + .copyRange(region, adapter, trueExpr) val idx = mkRegisterReading(4, sizeSort) val reading = concreteRegion.read(idx) - val key = region.regionId.keyMapper(translator)(keyConverter.convert(translator.translate(idx))) + val key = region.collectionId.keyMapper(translator)(adapter.convert(translator.translate(idx))) val innerReading = translator.translate(region.read(key)) val guard = @@ -183,11 +201,12 @@ class TranslationTest { val translated = translator.translate(reading) // 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() + KZ3Solver(this).use { solver -> + solver.assert(expected neq translated) + val status = solver.check() - assertSame(KSolverStatus.UNSAT, status) + assertSame(KSolverStatus.UNSAT, status) + } } @Test @@ -200,7 +219,8 @@ class TranslationTest { val g2 = mkRegisterReading(-2, boolSort) val g3 = mkRegisterReading(-3, boolSort) - val region = emptyInputFieldRegion(mockk(), bv32Sort) + val region = UInputFieldId(mockk(), bv32Sort) + .emptyRegion() .write(ref1, mkBv(1), g1) .write(ref2, mkBv(2), g2) .write(ref3, mkBv(3), g3) @@ -225,7 +245,8 @@ class TranslationTest { val ref2 = mkRegisterReading(2, addressSort) val ref3 = mkRegisterReading(3, addressSort) - val region = emptyInputArrayLengthRegion(mockk(), bv32Sort) + val region = UInputArrayLengthId(mockk(), bv32Sort) + .emptyRegion() .write(ref1, mkBv(1), trueExpr) .write(ref2, mkBv(2), trueExpr) .write(ref3, mkBv(3), trueExpr) @@ -246,7 +267,7 @@ class TranslationTest { @Test fun testTranslateInputToInputArrayCopy() = with(ctx) { - val ref1 = mkRegisterReading(0, addressSort) + val ref1 = mkRegisterReading(0, addressSort) as UHeapRef val idx1 = mkRegisterReading(1, sizeSort) val val1 = mkBv(1) @@ -254,35 +275,99 @@ class TranslationTest { val idx2 = mkRegisterReading(3, sizeSort) val val2 = mkBv(2) - val inputRegion1 = emptyInputArrayRegion(valueArrayDescr, bv32Sort) + val inputRegion1 = UInputArrayId(valueArrayDescr, bv32Sort) + .emptyRegion() .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 adapter = USymbolicArrayInputToInputCopyAdapter( + ref1 to mkSizeExpr(0), + ref1 to mkSizeExpr(0), + ref1 to mkSizeExpr(5), + USymbolicArrayIndexKeyInfo + ) + + var inputRegion2 = UInputArrayId(valueArrayDescr, bv32Sort).emptyRegion() 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) + .copyRange(inputRegion1, adapter, 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() + KZ3Solver(this).use { solver -> + solver.assert(translated) + val status = solver.check() - assertSame(KSolverStatus.UNSAT, status) + assertSame(KSolverStatus.UNSAT, status) + } + } + + @Test + fun testSymbolicMapRefKeyRead() = with(ctx) { + val concreteMapRef = heap.allocate() + val symbolicMapRef = mkRegisterReading(20, addressSort) + + runSymbolicMapRefKeyReadChecks(concreteMapRef) + runSymbolicMapRefKeyReadChecks(symbolicMapRef) + } + + private fun runSymbolicMapRefKeyReadChecks(mapRef: UHeapRef) = with(ctx) { + val otherConcreteMapRef = heap.allocate() + val otherSymbolicMapRef = mkRegisterReading(10, addressSort) + + val concreteRef0 = heap.allocate() + val concreteRef1 = heap.allocate() + val concreteRefMissed = heap.allocate() + + val symbolicRef0 = mkRegisterReading(0, addressSort) + val symbolicRef1 = mkRegisterReading(1, addressSort) + val symbolicRefMissed = mkRegisterReading(2, addressSort) + + var storedValue = 1 + for (ref in listOf(mapRef, otherConcreteMapRef, otherSymbolicMapRef)) { + for (keyRef in listOf(concreteRef0, concreteRef1, symbolicRef0, symbolicRef1)) { + val lValue = URefMapEntryLValue(valueFieldDescr.second, ref, keyRef, valueArrayDescr) + heap.write(lValue, mkBv(storedValue++), trueExpr) + } + } + + val concreteValue = heap.read( + URefMapEntryLValue(valueFieldDescr.second, mapRef, concreteRef0, valueArrayDescr) + ) + + val concreteMissed = heap.read( + URefMapEntryLValue(valueFieldDescr.second, mapRef, concreteRefMissed, valueArrayDescr) + ) + + val symbolicValue = heap.read( + URefMapEntryLValue(valueFieldDescr.second, mapRef, symbolicRef0, valueArrayDescr) + ) + + val symbolicMissed = heap.read( + URefMapEntryLValue(valueFieldDescr.second, mapRef, symbolicRefMissed, valueArrayDescr) + ) + + checkNoConcreteHeapRefs(concreteValue) + checkNoConcreteHeapRefs(concreteMissed) + checkNoConcreteHeapRefs(symbolicValue) + checkNoConcreteHeapRefs(symbolicMissed) + } + + private fun checkNoConcreteHeapRefs(expr: UExpr<*>) { + // Translator throws exception if concrete ref occurs + translator.translate(expr) } @Test fun testTranslateInputToInputArrayCopyAddressSort() = with(ctx) { - val ref1 = mkRegisterReading(0, addressSort) + val ref1 = mkRegisterReading(0, addressSort) as UHeapRef val idx1 = mkRegisterReading(1, sizeSort) val val1 = mkConcreteHeapRef(1) @@ -290,30 +375,37 @@ class TranslationTest { val idx2 = mkRegisterReading(3, sizeSort) val val2 = mkRegisterReading(5, addressSort) - val inputRegion1 = emptyInputArrayRegion(valueArrayDescr, addressSort) + val inputRegion1 = UInputArrayId(valueArrayDescr, addressSort) + .emptyRegion() .write(ref1 to idx1, val1, trueExpr) .write(ref2 to idx2, val2, trueExpr) + val adapter = USymbolicArrayInputToInputCopyAdapter( + ref1 to mkSizeExpr(0), + ref1 to mkSizeExpr(0), + ref1 to mkSizeExpr(5), + USymbolicArrayIndexKeyInfo + ) - val keyConverter = UInputToInputKeyConverter(ref1 to mkBv(0), ref1 to mkBv(0), mkBv(5)) - var inputRegion2 = emptyInputArrayRegion(mockk(), addressSort) + var inputRegion2 = UInputArrayId(valueArrayDescr, addressSort).emptyRegion() 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) + .copyRange(inputRegion1, adapter, 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() + KZ3Solver(this).use { solver -> + solver.assert(translated) + val status = solver.check() - assertSame(KSolverStatus.UNSAT, status) + assertSame(KSolverStatus.UNSAT, status) + } } @Test @@ -324,18 +416,23 @@ class TranslationTest { val idx2 = mkRegisterReading(3, sizeSort) val val2 = mkRegisterReading(5, addressSort) - val allocatedRegion1 = emptyAllocatedArrayRegion(valueArrayDescr, 1, addressSort) + val allocatedRegion1 = UAllocatedArrayId(valueArrayDescr, addressSort, 1) + .emptyRegion() .write(idx1, val1, trueExpr) .write(idx2, val2, trueExpr) - val keyConverter = UAllocatedToAllocatedKeyConverter(mkConcreteHeapRef(1) to mkBv(0), mkConcreteHeapRef(1) to mkBv(0), mkBv(5)) - var allocatedRegion2 = emptyAllocatedArrayRegion(mockk(), 2, addressSort) + val adapter = USymbolicArrayAllocatedToAllocatedCopyAdapter( + mkSizeExpr(0), mkSizeExpr(0), mkSizeExpr(5), USizeExprKeyInfo + ) + + var allocatedRegion2 = UAllocatedArrayId(valueArrayDescr, addressSort, 2) + .emptyRegion() val idx = mkRegisterReading(4, sizeSort) val readingBeforeCopy = allocatedRegion2.read(idx) allocatedRegion2 = allocatedRegion2 - .copyRange(allocatedRegion1, mkBv(0), mkBv(5), keyConverter, trueExpr) + .copyRange(allocatedRegion1, adapter, trueExpr) val readingAfterCopy = allocatedRegion2.read(idx) @@ -343,16 +440,18 @@ class TranslationTest { val expr = (readingBeforeCopy neq readingAfterCopy) and outsideOfCopy val translated = translator.translate(expr) - val solver = KZ3Solver(this) - solver.assert(translated) - val status = solver.check() + KZ3Solver(this).use { solver -> + solver.assert(translated) + val status = solver.check() - assertSame(KSolverStatus.UNSAT, status) + assertSame(KSolverStatus.UNSAT, status) + } } @Test fun testCachingOfTranslatedMemoryUpdates() = with(ctx) { - val allocatedRegion = emptyAllocatedArrayRegion(valueArrayDescr, 0, sizeSort) + val allocatedRegion = UAllocatedArrayId(valueArrayDescr, sizeSort, 0) + .emptyRegion() .write(mkRegisterReading(0, sizeSort), mkBv(0), trueExpr) .write(mkRegisterReading(1, sizeSort), mkBv(1), trueExpr) @@ -371,4 +470,4 @@ class TranslationTest { assertEquals(4, ctx.storeCallCounter) } -} \ No newline at end of file +} diff --git a/usvm-core/src/test/kotlin/org/usvm/types/TypeSolverTest.kt b/usvm-core/src/test/kotlin/org/usvm/types/TypeSolverTest.kt index ca89e61c6..6ba5aac78 100644 --- a/usvm-core/src/test/kotlin/org/usvm/types/TypeSolverTest.kt +++ b/usvm-core/src/test/kotlin/org/usvm/types/TypeSolverTest.kt @@ -11,13 +11,14 @@ import org.usvm.NULL_ADDRESS import org.usvm.UComponents import org.usvm.UConcreteHeapRef import org.usvm.UContext +import org.usvm.api.readField +import org.usvm.api.typeStreamOf +import org.usvm.api.writeField import org.usvm.constraints.UPathConstraints import org.usvm.isFalse import org.usvm.isTrue -import org.usvm.memory.UMemoryBase -import org.usvm.memory.URegionHeap -import org.usvm.memory.emptyInputArrayRegion -import org.usvm.memory.heapRefEq +import org.usvm.memory.UMemory +import org.usvm.collection.array.UInputArrayId import org.usvm.model.UModelBase import org.usvm.model.buildTranslatorAndLazyDecoder import org.usvm.solver.TypeSolverQuery @@ -52,14 +53,14 @@ import kotlin.test.assertTrue class TypeSolverTest { private val typeSystem = testTypeSystem - private val components = mockk>() + private val components = mockk>() private val ctx = UContext(components) - private val solver: USolverBase + private val solver: USolverBase private val typeSolver: UTypeSolver init { - val (translator, decoder) = buildTranslatorAndLazyDecoder(ctx) - val softConstraintsProvider = USoftConstraintsProvider(ctx) + val (translator, decoder) = buildTranslatorAndLazyDecoder(ctx) + val softConstraintsProvider = USoftConstraintsProvider(ctx) typeSolver = UTypeSolver(typeSystem) solver = USolverBase(ctx, KZ3Solver(ctx), typeSolver, translator, decoder, softConstraintsProvider) @@ -69,7 +70,7 @@ class TypeSolverTest { } private val pc = UPathConstraints(ctx) - private val memory = UMemoryBase(ctx, pc.typeConstraints) + private val memory = UMemory(ctx, pc.typeConstraints) @Test fun `Test concrete ref -- open type inheritance`() { @@ -83,7 +84,7 @@ class TypeSolverTest { val ref = mkRegisterReading(0, addressSort) pc += mkIsSubtypeExpr(ref, base1) pc += mkHeapRefEq(ref, nullRef).not() - val model = (solver.check(pc) as USatResult>).model + val model = (solver.check(pc) as USatResult>).model val concreteRef = assertIs(model.eval(ref)) val types = model.typeStreamOf(concreteRef) types.take100AndAssertEqualsToSetOf(base1, derived1A, derived1B) @@ -94,7 +95,7 @@ class TypeSolverTest { val ref = mkRegisterReading(0, addressSort) pc += mkIsSubtypeExpr(ref, interface1) pc += mkHeapRefEq(ref, nullRef).not() - val model = (solver.check(pc) as USatResult>).model + val model = (solver.check(pc) as USatResult>).model val concreteRef = assertIs(model.eval(ref)) val types = model.typeStreamOf(concreteRef) types.take100AndAssertEqualsToSetOf(derived1A, derivedMulti, derivedMultiInterfaces) @@ -123,12 +124,12 @@ class TypeSolverTest { pc += mkIsSubtypeExpr(ref, base2) val resultWithoutNullConstraint = solver.check(pc) - assertIs>>(resultWithoutNullConstraint) + assertIs>>(resultWithoutNullConstraint) pc += mkHeapRefEq(ref, nullRef).not() val resultWithNullConstraint = solver.check(pc) - assertIs>>(resultWithNullConstraint) + assertIs>>(resultWithNullConstraint) } @Test @@ -142,13 +143,13 @@ class TypeSolverTest { pc += mkHeapRefEq(ref1, nullRef).not() val resultWithoutEqConstraint = solver.check(pc) - val model = assertIs>>(resultWithoutEqConstraint).model + val model = assertIs>>(resultWithoutEqConstraint).model assertNotEquals(model.eval(ref0), model.eval(ref1)) pc += mkHeapRefEq(ref0, ref1) val resultWithEqConstraint = solver.check(pc) - assertIs>>(resultWithEqConstraint) + assertIs>>(resultWithEqConstraint) } @Test @@ -165,7 +166,7 @@ class TypeSolverTest { pc += mkHeapRefEq(ref2, nullRef).not() val resultWithoutEqConstraint = solver.check(pc) - val model = assertIs>>(resultWithoutEqConstraint).model + val model = assertIs>>(resultWithoutEqConstraint).model assertNotEquals(model.eval(ref0), model.eval(ref1)) pc += mkHeapRefEq(ref0, ref1) @@ -186,7 +187,7 @@ class TypeSolverTest { val resultWithoutEqConstraint = solver.check(pc) val modelWithoutEqConstraint = - assertIs>>(resultWithoutEqConstraint).model + assertIs>>(resultWithoutEqConstraint).model val concreteAddress0 = assertIs(modelWithoutEqConstraint.eval(ref0)).address val concreteAddress1 = assertIs(modelWithoutEqConstraint.eval(ref1)).address @@ -197,7 +198,7 @@ class TypeSolverTest { pc += mkHeapRefEq(ref0, ref1) val resultWithEqConstraint = solver.check(pc) - val modelWithEqConstraint = assertIs>>(resultWithEqConstraint).model + val modelWithEqConstraint = assertIs>>(resultWithEqConstraint).model assertEquals(mkConcreteHeapRef(NULL_ADDRESS), modelWithEqConstraint.eval(ref0)) assertEquals(mkConcreteHeapRef(NULL_ADDRESS), modelWithEqConstraint.eval(ref1)) @@ -205,7 +206,7 @@ class TypeSolverTest { pc += mkHeapRefEq(nullRef, ref0).not() val resultWithEqAndNotNullConstraint = solver.check(pc) - assertIs>>(resultWithEqAndNotNullConstraint) + assertIs>>(resultWithEqAndNotNullConstraint) } @Test @@ -229,7 +230,7 @@ class TypeSolverTest { with(pc.clone()) { val result = solver.check(this) - assertIs>>(result) + assertIs>>(result) val concreteA = result.model.eval(a) val concreteB1 = result.model.eval(b1) @@ -241,7 +242,7 @@ class TypeSolverTest { } with(pc.clone()) { - val model = mockk> { + val model = mockk> { every { eval(a) } returns mkConcreteHeapRef(INITIAL_INPUT_ADDRESS) every { eval(b1) } returns mkConcreteHeapRef(INITIAL_INPUT_ADDRESS) every { eval(b2) } returns mkConcreteHeapRef(INITIAL_INPUT_ADDRESS) @@ -262,7 +263,7 @@ class TypeSolverTest { with(pc.clone()) { this += mkHeapRefEq(a, c) and mkHeapRefEq(b1, c) val result = solver.check(this) - assertIs>>(result) + assertIs>>(result) } } @@ -280,9 +281,9 @@ class TypeSolverTest { pc += (mkHeapRefEq(a, c) or mkHeapRefEq(b, c)) and (!mkHeapRefEq(a, c) or !mkHeapRefEq(b, c)).not() val resultBeforeNotNullConstraints = solver.check(pc) - val model = assertIs>>(resultBeforeNotNullConstraints).model + val model = assertIs>>(resultBeforeNotNullConstraints).model - assertIs>>(resultBeforeNotNullConstraints) + assertIs>>(resultBeforeNotNullConstraints) val concreteA = assertIs(model.eval(a)).address val concreteB = assertIs(model.eval(b)).address @@ -293,7 +294,7 @@ class TypeSolverTest { pc += mkOrNoSimplify(mkHeapRefEq(a, nullRef).not(), falseExpr) val resultWithNotNullConstraints = solver.check(pc) - assertIs>>(resultWithNotNullConstraints) + assertIs>>(resultWithNotNullConstraints) } @Test @@ -312,7 +313,7 @@ class TypeSolverTest { pc += mkOrNoSimplify(mkHeapRefEq(a, nullRef).not(), falseExpr) val result = solver.check(pc) - val model = assertIs>>(result).model + val model = assertIs>>(result).model val concreteA = assertIs(model.eval(a)).address val concreteB = assertIs(model.eval(b)).address @@ -336,12 +337,13 @@ class TypeSolverTest { val idx2 = 0.toBv() val field = mockk() - val heap = URegionHeap(ctx) + val heap = UMemory(ctx, mockk()) heap.writeField(val1, field, bv32Sort, 1.toBv(), trueExpr) heap.writeField(val2, field, bv32Sort, 2.toBv(), trueExpr) - val inputRegion = emptyInputArrayRegion(mockk(), addressSort) + val inputRegion = UInputArrayId(mockk(), addressSort) + .emptyRegion() .write(arr1 to idx1, val1, trueExpr) .write(arr2 to idx2, val2, trueExpr) @@ -372,9 +374,9 @@ class TypeSolverTest { val ref = mkRegisterReading(0, addressSort) pc += mkHeapRefEq(ref, nullRef) or mkIsSubtypeExpr(ref, interfaceAB).not() - assertIs>>(solver.check(pc)) + assertIs>>(solver.check(pc)) - pc += heapRefEq(ref, nullRef).not() and (mkIsSubtypeExpr(ref, a) or mkIsSubtypeExpr(ref, b)) + pc += mkHeapRefEq(ref, nullRef).not() and (mkIsSubtypeExpr(ref, a) or mkIsSubtypeExpr(ref, b)) assertIs>(solver.check(pc)) } @@ -385,10 +387,10 @@ class TypeSolverTest { pc += mkIsSubtypeExpr(ref, a) or mkIsSubtypeExpr(ref, b) or mkIsSubtypeExpr(ref, c) pc += mkIsSubtypeExpr(ref, interfaceAB) xor unboundedBoolean val result1 = solver.check(pc) - assertIs>>(result1) + assertIs>>(result1) pc += unboundedBoolean val result2 = solver.check(pc) - val model = assertIs>>(result2).model + val model = assertIs>>(result2).model val concreteA = model.eval(ref) as UConcreteHeapRef val types = model.typeStreamOf(concreteA) types.take100AndAssertEqualsToSetOf(c) @@ -413,7 +415,7 @@ class TypeSolverTest { pc += ref1 neq ref2 val result = solver.check(pc) - val model = assertIs>>(result).model + val model = assertIs>>(result).model val concreteA = model.eval(ref1) val concreteB = model.eval(ref2) @@ -432,7 +434,7 @@ class TypeSolverTest { pc += mkIsSupertypeExpr(ref, derived1B) pc += mkIsSupertypeExpr(ref, derived1A) - val model = assertIs>>(solver.check(pc)).model + val model = assertIs>>(solver.check(pc)).model val concreteRef = model.eval(ref) as UConcreteHeapRef @@ -453,7 +455,7 @@ class TypeSolverTest { pc += mkIte(cond, mkIsSupertypeExpr(ref, derived1B), mkIsSupertypeExpr(ref, derived1A)) with(pc) { - val model = assertIs>>(solver.check(this)).model + val model = assertIs>>(solver.check(this)).model val concreteRef = model.eval(ref) as UConcreteHeapRef val typeStream = model.typeStreamOf(concreteRef) @@ -468,7 +470,7 @@ class TypeSolverTest { pc += mkIsSupertypeExpr(ref, derived1B).not() with(pc) { - val model = assertIs>>(solver.check(this)).model + val model = assertIs>>(solver.check(this)).model val concreteRef = model.eval(ref) as UConcreteHeapRef val typeStream = model.typeStreamOf(concreteRef) diff --git a/usvm-jvm/src/main/kotlin/org/usvm/api/util/JcTestResolver.kt b/usvm-jvm/src/main/kotlin/org/usvm/api/util/JcTestResolver.kt index d22c8ea05..5774c8445 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/api/util/JcTestResolver.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/api/util/JcTestResolver.kt @@ -3,7 +3,6 @@ package org.usvm.api.util import io.ksmt.utils.asExpr import org.jacodb.api.JcArrayType import org.jacodb.api.JcClassType -import org.jacodb.api.JcField import org.jacodb.api.JcPrimitiveType import org.jacodb.api.JcRefType import org.jacodb.api.JcType @@ -22,19 +21,15 @@ import org.jacodb.api.ext.void import org.usvm.INITIAL_CONCRETE_ADDRESS import org.usvm.INITIAL_INPUT_ADDRESS import org.usvm.NULL_ADDRESS -import org.usvm.UArrayIndexLValue -import org.usvm.UArrayLengthLValue import org.usvm.UConcreteHeapAddress import org.usvm.UConcreteHeapRef import org.usvm.UExpr -import org.usvm.UFieldLValue import org.usvm.UHeapRef -import org.usvm.ULValue -import org.usvm.URegisterLValue import org.usvm.USort import org.usvm.api.JcCoverage import org.usvm.api.JcParametersState import org.usvm.api.JcTest +import org.usvm.api.typeStreamOf import org.usvm.machine.JcContext import org.usvm.machine.extractBool import org.usvm.machine.extractByte @@ -47,7 +42,12 @@ import org.usvm.machine.extractShort import org.usvm.machine.state.JcMethodResult import org.usvm.machine.state.JcState import org.usvm.machine.state.localIdx -import org.usvm.memory.UReadOnlySymbolicMemory +import org.usvm.memory.ULValue +import org.usvm.memory.UReadOnlyMemory +import org.usvm.memory.URegisterStackLValue +import org.usvm.collection.array.UArrayIndexLValue +import org.usvm.collection.array.length.UArrayLengthLValue +import org.usvm.collection.field.UFieldLValue import org.usvm.model.UModelBase import org.usvm.types.first import org.usvm.types.firstOrNull @@ -115,8 +115,8 @@ class JcTestResolver( */ private class MemoryScope( private val ctx: JcContext, - private val model: UModelBase, - private val memory: UReadOnlySymbolicMemory, + private val model: UModelBase, + private val memory: UReadOnlyMemory, private val method: JcTypedMethod, private val classLoader: ClassLoader = ClassLoader.getSystemClassLoader(), ) { @@ -125,7 +125,7 @@ class JcTestResolver( fun resolveState(): JcParametersState { // TODO: now we need to explicitly evaluate indices of registers, because we don't have specific ULValues val thisInstance = if (!method.isStatic) { - val ref = URegisterLValue(ctx.addressSort, idx = 0) + val ref = URegisterStackLValue(ctx.addressSort, idx = 0) resolveLValue(ref, method.enclosingType) } else { null @@ -133,14 +133,14 @@ class JcTestResolver( val parameters = method.parameters.mapIndexed { idx, param -> val registerIdx = method.method.localIdx(idx) - val ref = URegisterLValue(ctx.typeToSort(param.type), registerIdx) + val ref = URegisterStackLValue(ctx.typeToSort(param.type), registerIdx) resolveLValue(ref, param.type) } return JcParametersState(thisInstance, parameters) } - fun resolveLValue(lvalue: ULValue, type: JcType): Any? { + fun resolveLValue(lvalue: ULValue<*, *>, type: JcType): Any? { val expr = memory.read(lvalue) return resolveExpr(expr, type) diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcComponents.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcComponents.kt index 8ad48dd26..fe2ffc6ce 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/JcComponents.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/JcComponents.kt @@ -3,8 +3,6 @@ package org.usvm.machine import io.ksmt.solver.yices.KYicesSolver import io.ksmt.solver.z3.KZ3Solver import io.ksmt.symfpu.solver.SymFpuSolver -import org.jacodb.api.JcField -import org.jacodb.api.JcMethod import org.jacodb.api.JcType import org.usvm.SolverType import org.usvm.UComponents @@ -17,11 +15,11 @@ import org.usvm.solver.UTypeSolver class JcComponents( private val typeSystem: JcTypeSystem, private val solverType: SolverType -) : UComponents { +) : UComponents { private val closeableResources = mutableListOf() - override fun mkSolver(ctx: Context): USolverBase { - val (translator, decoder) = buildTranslatorAndLazyDecoder(ctx) - val softConstraintsProvider = USoftConstraintsProvider(ctx) + override fun mkSolver(ctx: Context): USolverBase { + val (translator, decoder) = buildTranslatorAndLazyDecoder(ctx) + val softConstraintsProvider = USoftConstraintsProvider(ctx) val smtSolver = when (solverType) { diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt index 6ecba8557..27e70806e 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcExprResolver.kt @@ -3,6 +3,7 @@ package org.usvm.machine.interpreter import io.ksmt.expr.KExpr import io.ksmt.utils.asExpr import io.ksmt.utils.cast +import io.ksmt.utils.uncheckedCast import org.jacodb.api.JcArrayType import org.jacodb.api.JcMethod import org.jacodb.api.JcPrimitiveType @@ -80,18 +81,14 @@ import org.jacodb.api.ext.objectType import org.jacodb.api.ext.short import org.jacodb.impl.bytecode.JcFieldImpl import org.jacodb.impl.types.FieldInfo -import org.usvm.UArrayIndexLValue -import org.usvm.UArrayLengthLValue import org.usvm.UBvSort import org.usvm.UConcreteHeapRef import org.usvm.UExpr -import org.usvm.UFieldLValue import org.usvm.UHeapRef -import org.usvm.ULValue -import org.usvm.URegisterLValue import org.usvm.USizeExpr import org.usvm.USizeSort import org.usvm.USort +import org.usvm.api.allocateArrayInitialized import org.usvm.isTrue import org.usvm.machine.JcContext import org.usvm.machine.operator.JcBinaryOperator @@ -102,7 +99,13 @@ import org.usvm.machine.operator.wideTo32BitsIfNeeded import org.usvm.machine.state.JcMethodResult import org.usvm.machine.state.JcState import org.usvm.machine.state.throwExceptionWithoutStackFrameDrop +import org.usvm.memory.ULValue +import org.usvm.memory.URegisterStackLValue +import org.usvm.collection.array.UArrayIndexLValue +import org.usvm.collection.array.length.UArrayLengthLValue +import org.usvm.collection.field.UFieldLValue import org.usvm.util.extractJcRefType +import org.usvm.util.write /** * An expression resolver based on JacoDb 3-address code. A result of resolving is `null`, iff @@ -136,7 +139,7 @@ class JcExprResolver( * * @see JcStepScope */ - fun resolveLValue(value: JcValue): ULValue? = + fun resolveLValue(value: JcValue): ULValue<*, *>? = when (value) { is JcFieldRef -> resolveFieldRef(value.instance, value.field) is JcArrayAccess -> resolveArrayAccess(value.array, value.index) @@ -285,7 +288,11 @@ class JcExprResolver( val valuesArrayDescriptor = arrayDescriptorOf(stringValueField.fieldType as JcArrayType) val elementType = requireNotNull(stringValueField.fieldType.ifArrayGetElementType) - val charArrayRef = memory.malloc(valuesArrayDescriptor, typeToSort(elementType), charValues) + val charArrayRef = memory.allocateArrayInitialized( + valuesArrayDescriptor, + typeToSort(elementType), + charValues.uncheckedCast() + ) // overwrite array type because descriptor is element type memory.types.allocate(charArrayRef.address, stringValueField.fieldType) @@ -336,7 +343,7 @@ class JcExprResolver( override fun visitJcInstanceOfExpr(expr: JcInstanceOfExpr): UExpr? = with(ctx) { val ref = resolveJcExpr(expr.operand)?.asExpr(addressSort) ?: return null scope.calcOnState { - val notEqualsNull = mkHeapRefEq(ref, memory.heap.nullRef()).not() + val notEqualsNull = mkHeapRefEq(ref, memory.nullRef()).not() val isExpr = memory.types.evalIsSubtype(ref, expr.targetType) mkAnd(notEqualsNull, isExpr) } @@ -357,8 +364,15 @@ class JcExprResolver( val size = resolveCast(expr.dimensions[0], ctx.cp.int)?.asExpr(bv32Sort) ?: return null // TODO: other dimensions ( > 1) checkNewArrayLength(size) ?: return null - val ref = scope.calcOnState { memory.malloc(expr.type, size) } - ref + + scope.calcOnState { + val ref = memory.alloc(expr.type) + + val arrayDescriptor = arrayDescriptorOf(expr.type as JcArrayType) + memory.write(UArrayLengthLValue(ref, arrayDescriptor), size) + + ref + } } override fun visitJcNewExpr(expr: JcNewExpr): UExpr = @@ -525,7 +539,7 @@ class JcExprResolver( // region lvalue resolving - private fun resolveFieldRef(instance: JcValue?, field: JcTypedField): ULValue? = + private fun resolveFieldRef(instance: JcValue?, field: JcTypedField): ULValue<*, *>? = ensureStaticFieldsInitialized(field.enclosingType) { with(ctx) { if (instance != null) { @@ -591,12 +605,12 @@ class JcExprResolver( private fun staticFieldsInitializedFlag(type: JcRefType, classRef: UHeapRef) = UFieldLValue( - fieldSort = ctx.booleanSort, + sort = ctx.booleanSort, field = JcFieldImpl(type.jcClass, staticFieldsInitializedFlagField), ref = classRef ) - private fun resolveArrayAccess(array: JcValue, index: JcValue): UArrayIndexLValue? = with(ctx) { + private fun resolveArrayAccess(array: JcValue, index: JcValue): UArrayIndexLValue? = with(ctx) { val arrayRef = resolveJcExpr(array)?.asExpr(addressSort) ?: return null checkNullPointer(arrayRef) ?: return null @@ -616,11 +630,11 @@ class JcExprResolver( return UArrayIndexLValue(cellSort, arrayRef, idx, arrayDescriptor) } - private fun resolveLocal(local: JcLocal): URegisterLValue { + private fun resolveLocal(local: JcLocal): URegisterStackLValue<*> { val method = requireNotNull(scope.calcOnState { lastEnteredMethod }) val localIdx = localToIdx(method, local) val sort = ctx.typeToSort(local.type) - return URegisterLValue(sort, localIdx) + return URegisterStackLValue(sort, localIdx) } // endregion diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt index 42bc10088..347bca897 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInterpreter.kt @@ -5,7 +5,6 @@ import mu.KLogging import org.jacodb.api.JcArrayType import org.jacodb.api.JcClassOrInterface import org.jacodb.api.JcClassType -import org.jacodb.api.JcField import org.jacodb.api.JcMethod import org.jacodb.api.JcPrimitiveType import org.jacodb.api.JcRefType @@ -32,7 +31,7 @@ import org.usvm.StepScope import org.usvm.UBoolExpr import org.usvm.UConcreteHeapRef import org.usvm.UInterpreter -import org.usvm.URegisterLValue +import org.usvm.api.allocate import org.usvm.machine.JcApplicationGraph import org.usvm.machine.JcContext import org.usvm.machine.state.JcMethodResult @@ -45,9 +44,11 @@ import org.usvm.machine.state.parametersWithThisCount import org.usvm.machine.state.returnValue import org.usvm.machine.state.throwExceptionAndDropStackFrame import org.usvm.machine.state.throwExceptionWithoutStackFrameDrop +import org.usvm.memory.URegisterStackLValue import org.usvm.solver.USatResult +import org.usvm.util.write -typealias JcStepScope = StepScope +typealias JcStepScope = StepScope /** * A JacoDB interpreter. @@ -69,7 +70,7 @@ class JcInterpreter( if (!method.isStatic) { with(ctx) { - val thisLValue = URegisterLValue(addressSort, 0) + val thisLValue = URegisterStackLValue(addressSort, 0) val ref = state.memory.read(thisLValue).asExpr(addressSort) state.pathConstraints += mkEq(ref, nullRef).not() state.pathConstraints += mkIsSubtypeExpr(ref, typedMethod.enclosingType) @@ -80,14 +81,14 @@ class JcInterpreter( with(ctx) { val type = typedParameter.type if (type is JcRefType) { - val argumentLValue = URegisterLValue(typeToSort(type), method.localIdx(idx)) + val argumentLValue = URegisterStackLValue(typeToSort(type), method.localIdx(idx)) val ref = state.memory.read(argumentLValue).asExpr(addressSort) state.pathConstraints += mkIsSubtypeExpr(ref, type) } } } - val solver = ctx.solver() + val solver = ctx.solver() val model = (solver.checkWithSoftConstraints(state.pathConstraints) as USatResult).model state.models = listOf(model) @@ -305,7 +306,7 @@ class JcInterpreter( private fun stringConstantAllocator(value: String, state: JcState): UConcreteHeapRef = stringConstantAllocatedRefs.getOrPut(value) { // Allocate globally unique ref - state.memory.heap.allocate() + state.memory.allocate() } private val typeInstanceAllocatedRefs = mutableMapOf() @@ -314,7 +315,7 @@ class JcInterpreter( val typeInfo = resolveTypeInfo(type) return typeInstanceAllocatedRefs.getOrPut(typeInfo) { // Allocate globally unique ref - state.memory.heap.allocate() + state.memory.allocate() } } diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInvokeResolver.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInvokeResolver.kt index 5153717d5..1d0261faa 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInvokeResolver.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/interpreter/JcInvokeResolver.kt @@ -13,6 +13,7 @@ import org.usvm.UBoolExpr import org.usvm.UConcreteHeapRef import org.usvm.UExpr import org.usvm.USort +import org.usvm.api.typeStreamOf import org.usvm.machine.JcApplicationGraph import org.usvm.machine.JcContext import org.usvm.machine.state.JcState diff --git a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt index d0f78281e..9df90e15f 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/machine/state/JcState.kt @@ -1,26 +1,25 @@ package org.usvm.machine.state -import org.jacodb.api.JcField import org.jacodb.api.JcMethod import org.jacodb.api.JcType import org.jacodb.api.cfg.JcInst +import org.usvm.PathsTrieNode import org.usvm.UCallStack import org.usvm.UState import org.usvm.constraints.UPathConstraints import org.usvm.machine.JcContext -import org.usvm.memory.UMemoryBase +import org.usvm.memory.UMemory import org.usvm.model.UModelBase -import org.usvm.PathsTrieNode class JcState( ctx: JcContext, callStack: UCallStack = UCallStack(), pathConstraints: UPathConstraints = UPathConstraints(ctx), - memory: UMemoryBase = UMemoryBase(ctx, pathConstraints.typeConstraints), - models: List> = listOf(), + memory: UMemory = UMemory(ctx, pathConstraints.typeConstraints), + models: List> = listOf(), override var pathLocation: PathsTrieNode = ctx.mkInitialLocation(), var methodResult: JcMethodResult = JcMethodResult.NoCall, -) : UState( +) : UState( ctx, callStack, pathConstraints, diff --git a/usvm-jvm/src/main/kotlin/org/usvm/util/Utils.kt b/usvm-jvm/src/main/kotlin/org/usvm/util/Utils.kt index 007450ac0..8919648d5 100644 --- a/usvm-jvm/src/main/kotlin/org/usvm/util/Utils.kt +++ b/usvm-jvm/src/main/kotlin/org/usvm/util/Utils.kt @@ -2,9 +2,19 @@ package org.usvm.util import org.jacodb.api.JcRefType import org.jacodb.api.JcType +import org.usvm.UExpr +import org.usvm.USort import org.usvm.machine.JcContext +import org.usvm.memory.ULValue +import org.usvm.memory.UWritableMemory +import org.usvm.uctx import kotlin.reflect.KClass fun JcContext.extractJcType(clazz: KClass<*>): JcType = cp.findTypeOrNull(clazz.qualifiedName!!)!! -fun JcContext.extractJcRefType(clazz: KClass<*>): JcRefType = extractJcType(clazz) as JcRefType \ No newline at end of file +fun JcContext.extractJcRefType(clazz: KClass<*>): JcRefType = extractJcType(clazz) as JcRefType + +@Suppress("UNCHECKED_CAST") +fun UWritableMemory<*>.write(ref: ULValue<*, *>, value: UExpr<*>) { + write(ref as ULValue<*, USort>, value as UExpr, value.uctx.trueExpr) +} diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/language/Domain.kt b/usvm-sample-language/src/main/kotlin/org/usvm/language/Domain.kt index 6491d2fe7..6937a9275 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/language/Domain.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/language/Domain.kt @@ -22,14 +22,14 @@ class Body( val stmts: List ) -class Struct( +data class Struct( val name: String, val fields: Set> ) { override fun toString() = name } -class Field( +data class Field( val name: String, val type: T, ) { diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/language/Types.kt b/usvm-sample-language/src/main/kotlin/org/usvm/language/Types.kt index 5b4ddb685..c63bb50f4 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/language/Types.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/language/Types.kt @@ -14,7 +14,7 @@ object BooleanType : PrimitiveType { sealed interface RefType : SampleType -class StructType( +data class StructType( val struct: Struct ) : RefType { override fun toString(): String = struct.name @@ -22,7 +22,7 @@ class StructType( val Null = Struct("null", emptySet()) -class ArrayType( +data class ArrayType( val elementType: T ) : RefType { override fun toString(): String = "[]$elementType" diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/ResultModelConverter.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/ResultModelConverter.kt index 0ba56303e..1320910b3 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/ResultModelConverter.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/ResultModelConverter.kt @@ -9,13 +9,15 @@ import org.usvm.UBv32Sort import org.usvm.UContext import org.usvm.UExpr import org.usvm.USort +import org.usvm.api.readArrayIndex +import org.usvm.api.readArrayLength +import org.usvm.api.readField import org.usvm.isTrue import org.usvm.language.ArrayCreation import org.usvm.language.ArrayType import org.usvm.language.BooleanConst import org.usvm.language.BooleanType import org.usvm.language.Expr -import org.usvm.language.Field import org.usvm.language.IntConst import org.usvm.language.IntType import org.usvm.language.Method @@ -56,7 +58,7 @@ class ResultModelConverter( private class InputScope( private val ctx: UContext, - private val model: UModelBase, SampleType>, + private val model: UModelBase, ) { fun convertExpr(expr: UExpr, type: SampleType): Expr = when (type) { @@ -81,7 +83,7 @@ class ResultModelConverter( } val fieldValues = type.struct.fields.associateWith { field -> val sort = ctx.typeToSort(field.type) - val fieldUExpr = model.heap.readField(ref, field, sort) + val fieldUExpr = model.readField(ref, field, sort) convertExpr(fieldUExpr, field.type) } return StructCreation(type.struct, fieldValues.toList()) @@ -94,10 +96,10 @@ class ResultModelConverter( if (ref == ctx.mkConcreteHeapRef(NULL_ADDRESS)) { return ArrayCreation(StructType(Null), IntConst(0), emptyList()) } - val lengthUExpr = model.heap.readArrayLength(ref, type) + val lengthUExpr = model.readArrayLength(ref, type) val length = (convertExpr(lengthUExpr, IntType) as IntConst).const val resolved = (0 until length).map { idx -> - val indexUExpr = model.heap.readArrayIndex(ref, ctx.mkBv(idx), type, ctx.typeToSort(type.elementType)) + val indexUExpr = model.readArrayIndex(ref, ctx.mkBv(idx), type, ctx.typeToSort(type.elementType)) convertExpr(indexUExpr, type.elementType) } return ArrayCreation(type.elementType, IntConst(length), resolved) diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleExprResolver.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleExprResolver.kt index e41804563..e874e73ab 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleExprResolver.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleExprResolver.kt @@ -3,18 +3,14 @@ package org.usvm.machine import io.ksmt.expr.KBitVec32Value import io.ksmt.expr.KExpr import io.ksmt.utils.asExpr -import org.usvm.UArrayIndexLValue -import org.usvm.UArrayLengthLValue import org.usvm.UBoolExpr import org.usvm.UBv32Sort import org.usvm.UContext import org.usvm.UExpr -import org.usvm.UFieldLValue import org.usvm.UHeapRef -import org.usvm.ULValue -import org.usvm.URegisterLValue import org.usvm.USizeExpr import org.usvm.USort +import org.usvm.api.allocateArray import org.usvm.language.And import org.usvm.language.ArrayCreation import org.usvm.language.ArrayEq @@ -60,6 +56,11 @@ import org.usvm.language.StructExpr import org.usvm.language.StructIsNull import org.usvm.language.StructType import org.usvm.language.UnaryMinus +import org.usvm.memory.ULValue +import org.usvm.memory.URegisterStackLValue +import org.usvm.collection.array.UArrayIndexLValue +import org.usvm.collection.array.length.UArrayLengthLValue +import org.usvm.collection.field.UFieldLValue /** * Resolves [Expr]s to [UExpr]s, forks in the [scope] respecting unsats. Checks for exceptions. @@ -110,7 +111,7 @@ class SampleExprResolver( val size = resolveInt(expr.size) ?: return null checkArrayLength(size, expr.values.size) ?: return null - val ref = scope.calcOnState { memory.malloc(expr.type, size) } + val ref = scope.calcOnState { memory.allocateArray(expr.type, size) } val cellSort = typeToSort(expr.type.elementType) @@ -271,7 +272,7 @@ class SampleExprResolver( } } - fun resolveLValue(value: LValue): ULValue? = + fun resolveLValue(value: LValue): ULValue<*, *>? = when (value) { is ArrayIdxSetLValue -> resolveArraySelectRef(value.array, value.index) is FieldSetLValue -> resolveFieldSelectRef(value.instance, value.field) @@ -294,7 +295,7 @@ class SampleExprResolver( return scope.calcOnState { memory.read(fieldRef) } } - private fun resolveArraySelectRef(array: ArrayExpr<*>, index: IntExpr): ULValue? { + private fun resolveArraySelectRef(array: ArrayExpr<*>, index: IntExpr): ULValue<*, *>? { val arrayRef = resolveArray(array) ?: return null checkNullPointer(arrayRef) ?: return null @@ -311,7 +312,7 @@ class SampleExprResolver( return UArrayIndexLValue(cellSort, arrayRef, idx, array.type) } - private fun resolveFieldSelectRef(instance: StructExpr, field: Field<*>): ULValue? { + private fun resolveFieldSelectRef(instance: StructExpr, field: Field<*>): ULValue<*, *>? { val instanceRef = resolveStruct(instance) ?: return null checkNullPointer(instanceRef) ?: return null @@ -319,11 +320,11 @@ class SampleExprResolver( return UFieldLValue(sort, instanceRef, field) } - private fun resolveRegisterRef(register: Register<*>): ULValue { + private fun resolveRegisterRef(register: Register<*>): ULValue<*, *> { val localIdx = register.idx val type = register.type val sort = ctx.typeToSort(type) - return URegisterLValue(sort, localIdx) + return URegisterStackLValue(sort, localIdx) } private fun checkArrayIndex(idx: USizeExpr, length: USizeExpr) = with(ctx) { diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleInterpreter.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleInterpreter.kt index a47f367c9..904bce8c2 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleInterpreter.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleInterpreter.kt @@ -6,7 +6,6 @@ import org.usvm.StepScope import org.usvm.UContext import org.usvm.UInterpreter import org.usvm.language.Call -import org.usvm.language.Field import org.usvm.language.Goto import org.usvm.language.If import org.usvm.language.Return @@ -14,7 +13,7 @@ import org.usvm.language.SampleType import org.usvm.language.SetLabel import org.usvm.language.SetValue -typealias SampleStepScope = StepScope, UContext> +typealias SampleStepScope = StepScope val logger = object : KLogging() {}.logger diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleLanguageComponents.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleLanguageComponents.kt index c476492bd..9d60c5004 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleLanguageComponents.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleLanguageComponents.kt @@ -5,22 +5,20 @@ import io.ksmt.solver.z3.KZ3Solver import org.usvm.SolverType import org.usvm.UComponents import org.usvm.UContext -import org.usvm.types.UTypeSystem -import org.usvm.language.Field -import org.usvm.language.Method import org.usvm.language.SampleType import org.usvm.model.buildTranslatorAndLazyDecoder import org.usvm.solver.USoftConstraintsProvider import org.usvm.solver.USolverBase import org.usvm.solver.UTypeSolver +import org.usvm.types.UTypeSystem class SampleLanguageComponents( private val typeSystem: SampleTypeSystem, private val solverType: SolverType -) : UComponents, SampleType, Method<*>> { - override fun mkSolver(ctx: Context): USolverBase, SampleType, Method<*>, Context> { - val (translator, decoder) = buildTranslatorAndLazyDecoder, SampleType, Method<*>>(ctx) - val softConstraintsProvider = USoftConstraintsProvider, SampleType>(ctx) +) : UComponents { + override fun mkSolver(ctx: Context): USolverBase { + val (translator, decoder) = buildTranslatorAndLazyDecoder(ctx) + val softConstraintsProvider = USoftConstraintsProvider(ctx) val solver = when (solverType) { diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt index e1a0b6849..4ef7c9e4d 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleMachine.kt @@ -4,7 +4,6 @@ import kotlinx.collections.immutable.persistentListOf import org.usvm.UContext import org.usvm.UMachine import org.usvm.UMachineOptions -import org.usvm.language.Field import org.usvm.language.Method import org.usvm.language.Program import org.usvm.language.SampleType @@ -29,7 +28,7 @@ class SampleMachine( private val typeSystem = SampleTypeSystem() private val components = SampleLanguageComponents(typeSystem, options.solverType) private val ctx = UContext(components) - private val solver = ctx.solver, SampleType, Method<*>, UContext>() + private val solver = ctx.solver() private val interpreter = SampleInterpreter(ctx, applicationGraph) private val resultModelConverter = ResultModelConverter(ctx) diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleState.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleState.kt index 52a4d96c1..64f081ccf 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleState.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/SampleState.kt @@ -1,32 +1,31 @@ package org.usvm.machine +import org.usvm.PathsTrieNode import org.usvm.UCallStack import org.usvm.UContext import org.usvm.UExpr import org.usvm.USort import org.usvm.UState import org.usvm.constraints.UPathConstraints -import org.usvm.language.Field import org.usvm.language.Method import org.usvm.language.ProgramException import org.usvm.language.SampleType import org.usvm.language.Stmt import org.usvm.language.argumentCount import org.usvm.language.localsCount -import org.usvm.memory.UMemoryBase +import org.usvm.memory.UMemory import org.usvm.model.UModelBase -import org.usvm.PathsTrieNode class SampleState( ctx: UContext, callStack: UCallStack, Stmt> = UCallStack(), pathConstraints: UPathConstraints = UPathConstraints(ctx), - memory: UMemoryBase, SampleType, Method<*>> = UMemoryBase(ctx, pathConstraints.typeConstraints), - models: List, SampleType>> = listOf(), + memory: UMemory> = UMemory(ctx, pathConstraints.typeConstraints), + models: List> = listOf(), pathLocation: PathsTrieNode = ctx.mkInitialLocation(), var returnRegister: UExpr? = null, var exceptionRegister: ProgramException? = null, -) : UState, Method<*>, Stmt, UContext, SampleState>( +) : UState, Stmt, UContext, SampleState>( ctx, callStack, pathConstraints, diff --git a/usvm-sample-language/src/main/kotlin/org/usvm/machine/Utils.kt b/usvm-sample-language/src/main/kotlin/org/usvm/machine/Utils.kt index e6b46eccd..07aff6d5a 100644 --- a/usvm-sample-language/src/main/kotlin/org/usvm/machine/Utils.kt +++ b/usvm-sample-language/src/main/kotlin/org/usvm/machine/Utils.kt @@ -1,15 +1,25 @@ package org.usvm.machine import org.usvm.UContext +import org.usvm.UExpr +import org.usvm.USort import org.usvm.language.ArrayType import org.usvm.language.BooleanType import org.usvm.language.IntType import org.usvm.language.SampleType import org.usvm.language.StructType +import org.usvm.memory.ULValue +import org.usvm.memory.UWritableMemory +import org.usvm.uctx fun UContext.typeToSort(type: SampleType) = when (type) { BooleanType -> boolSort IntType -> bv32Sort is ArrayType<*> -> addressSort is StructType -> addressSort -} \ No newline at end of file +} + +@Suppress("UNCHECKED_CAST") +fun UWritableMemory<*>.write(ref: ULValue<*, *>, value: UExpr<*>) { + write(ref as ULValue<*, USort>, value as UExpr, value.uctx.trueExpr) +} diff --git a/usvm-util/src/main/kotlin/org/usvm/util/Regions.kt b/usvm-util/src/main/kotlin/org/usvm/util/Regions.kt index b26ee266e..f9adbd918 100644 --- a/usvm-util/src/main/kotlin/org/usvm/util/Regions.kt +++ b/usvm-util/src/main/kotlin/org/usvm/util/Regions.kt @@ -9,12 +9,15 @@ enum class RegionComparisonResult { interface Region { val isEmpty: Boolean fun compare(other: T): RegionComparisonResult + fun union(other: T): T fun subtract(other: T): T fun intersect(other: T): T } class TrivialRegion: Region { override val isEmpty = false + override fun union(other: TrivialRegion): TrivialRegion = this + override fun intersect(other: TrivialRegion): TrivialRegion = this override fun subtract(other: TrivialRegion): TrivialRegion = throw UnsupportedOperationException("TrivialRegion.subtract should not be called") @@ -159,7 +162,7 @@ data class Intervals>(private val points: List): Intervals { + override fun union(other: Intervals): Intervals { fun visit(x: Endpoint, inside1: Boolean, inside2: Boolean) = if (inside1 || inside2) null else x return combineWith(other, ::visit, ::visit, ::visit, ::visit, ::visit, ::visit) } @@ -232,6 +235,9 @@ data class SetRegion(private val points: Set, private val thrown: } override val isEmpty: Boolean = points.isEmpty() && !thrown + override fun union(other: SetRegion): SetRegion { + TODO("Not yet implemented") + } override fun compare(other: SetRegion): RegionComparisonResult = when { @@ -264,6 +270,12 @@ data class SetRegion(private val points: Set, private val thrown: else -> throw Exception("Unreachable") } + fun map(mapper: (Point) -> OtherPoint): SetRegion { + val otherPoints = mutableSetOf() + points.mapTo(otherPoints, mapper) + return SetRegion(otherPoints, thrown) + } + override fun toString(): String = "${if (thrown) "Z \\ " else ""}{${points.joinToString(", ") }}" } @@ -313,6 +325,10 @@ data class ProductRegion, Y: Region>(val products: List): ProductRegion { + TODO("Union this.products with other.products. Remove subsumed rectangles, merge mergeable.") + } + override fun compare(other: ProductRegion): RegionComparisonResult { // TODO: comparison actually computes difference. Reuse it somehow (for example, return difference together with verdict). val diff = other.subtract(this)