Skip to content

Commit fcb3f5f

Browse files
authored
Fix splitting for range updates (#291)
* fix splitting for range updates * fix imports
1 parent 5719f00 commit fcb3f5f

File tree

2 files changed

+73
-28
lines changed

2 files changed

+73
-28
lines changed

usvm-core/src/main/kotlin/org/usvm/memory/SymbolicCollectionUpdates.kt

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package org.usvm.memory
22

3+
import kotlinx.collections.immutable.PersistentMap
34
import kotlinx.collections.immutable.persistentMapOf
4-
import kotlinx.collections.immutable.toPersistentMap
55
import org.usvm.UBoolExpr
66
import org.usvm.UComposer
77
import org.usvm.UExpr
@@ -317,44 +317,52 @@ data class UTreeUpdates<Key, Reg : Region<Reg>, Sort : USort>(
317317
guardBuilder: GuardBuilder,
318318
composer: UComposer<*, *>?,
319319
): UTreeUpdates<Key, Reg, Sort> {
320-
var restRecordsAreIrrelevant = false
321-
val symbolicSubtreesStack =
322-
mutableListOf<Pair<Reg, Pair<UUpdateNode<Key, Sort>, RegionTree<Reg, UUpdateNode<Key, Sort>>>>>()
323-
324-
fun traverseTreeWhilePredicateSatisfied(tree: RegionTree<Reg, UUpdateNode<Key, Sort>>) {
320+
// returns fully symbolic tree entries after splitting
321+
fun traverseTreeWhilePredicateSatisfied(
322+
tree: RegionTree<Reg, UUpdateNode<Key, Sort>>,
323+
): List<Pair<Reg, Pair<UUpdateNode<Key, Sort>, RegionTree<Reg, UUpdateNode<Key, Sort>>>>> {
324+
val symbolicEntries =
325+
mutableListOf<Pair<Reg, Pair<UUpdateNode<Key, Sort>, RegionTree<Reg, UUpdateNode<Key, Sort>>>>>()
325326
// process nodes of the same level from newest to oldest
326-
for ((reg, entry) in tree.entries.toList().reversed()) {
327+
for ((reg, entry) in tree.entries.toList().asReversed()) {
327328
val (node, subtree) = entry
328329
val splitUpdate = node.split(key, predicate, matchingWrites, guardBuilder, composer)
329-
// range nodes are considered to belong to invariant tree since they can contain concretes
330-
if (splitUpdate === null || node is URangedUpdateNode<*, *, Key, Sort>) {
331-
traverseTreeWhilePredicateSatisfied(subtree)
332-
if (restRecordsAreIrrelevant) break
333-
} else {
334-
// since [splitWrite] preserves invariant that records satisfying predicate are always
335-
// at the top of the tree we can just add rest branch to splitTreeEntries
336-
symbolicSubtreesStack += reg to entry
330+
when (splitUpdate) {
331+
null -> {
332+
val restSymbolicTree = traverseTreeWhilePredicateSatisfied(subtree)
333+
symbolicEntries += restSymbolicTree
334+
}
335+
// range nodes are considered to belong to concrete subtree since they can contain concretes
336+
is URangedUpdateNode<*, *, Key, Sort> -> {
337+
val restSymbolicEntries = traverseTreeWhilePredicateSatisfied(subtree)
338+
val restTree = RegionTree(restSymbolicEntries.asReversed().toPersistentMap())
339+
symbolicEntries.add(reg to (splitUpdate to restTree))
340+
}
341+
342+
else -> {
343+
// since [splitWrite] preserves invariant that records satisfying predicate are always
344+
// at the top of the tree we can just add rest branch to splitTreeEntries
345+
symbolicEntries.add(reg to entry)
346+
}
337347
}
338348

339-
if (guardBuilder.isFalse) {
340-
restRecordsAreIrrelevant = true
341-
break
342-
}
349+
if (guardBuilder.isFalse) break
343350
}
351+
352+
return symbolicEntries
344353
}
345354

346-
traverseTreeWhilePredicateSatisfied(updates)
355+
val symbolicEntries = traverseTreeWhilePredicateSatisfied(updates)
347356

348-
val splitTreeEntries =
349-
mutableMapOf<Reg, Pair<UUpdateNode<Key, Sort>, RegionTree<Reg, UUpdateNode<Key, Sort>>>>()
357+
return this.copy(updates = RegionTree(symbolicEntries.asReversed().toPersistentMap()))
358+
}
350359

351-
// reconstruct region tree, including all updates unsatisfying `predicate(update.value(key))` in the same order
352-
while (symbolicSubtreesStack.isNotEmpty()) {
353-
val (reg, entry) = symbolicSubtreesStack.removeLast()
354-
splitTreeEntries[reg] = entry
360+
private fun <K, V> List<Pair<K, V>>.toPersistentMap(): PersistentMap<K, V> {
361+
val builder = persistentMapOf<K, V>().builder()
362+
for ((reg, tree) in this) {
363+
builder[reg] = tree
355364
}
356-
357-
return this.copy(updates = RegionTree(splitTreeEntries.toPersistentMap()))
365+
return builder.build()
358366
}
359367

360368
override fun splitWrite(

usvm-core/src/test/kotlin/org/usvm/memory/HeapRefSplittingTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,4 +445,41 @@ class HeapRefSplittingTest {
445445

446446
return array to ref
447447
}
448+
449+
@Test
450+
fun testRangedUpdateWithMixedWrite() = with(ctx) {
451+
val ref = allocateConcreteRef()
452+
val idx0 = mkSizeExpr(0)
453+
val idx1 = mkSizeExpr(1)
454+
val idx2 = mkSizeExpr(2)
455+
456+
val concreteValue1 = allocateConcreteRef()
457+
val symbolicValue2 = mkRegisterReading(0, addressSort)
458+
val concreteValue3 = allocateConcreteRef()
459+
460+
heap.writeArrayIndex(ref, idx0, arrayDescr.first, arrayDescr.second, concreteValue1, trueExpr)
461+
heap.writeArrayIndex(ref, idx1, arrayDescr.first, arrayDescr.second, symbolicValue2, trueExpr)
462+
heap.writeArrayIndex(ref, idx2, arrayDescr.first, arrayDescr.second, concreteValue3, trueExpr)
463+
464+
heap.memcpy(ref, ref, arrayDescr.first, arrayDescr.second, idx1, idx0, idx1)
465+
466+
val read = heap.readArrayIndex(ref, idx0, arrayDescr.first, arrayDescr.second)
467+
assertSame(symbolicValue2, read)
468+
}
469+
470+
@Test
471+
fun testClone() = with(ctx) {
472+
val ref = allocateConcreteRef()
473+
val idx = mkSizeExpr(2)
474+
475+
val concreteValue = allocateConcreteRef()
476+
477+
heap.writeArrayIndex(ref, idx, arrayDescr.first, arrayDescr.second, concreteValue, trueExpr)
478+
479+
val copyRef = allocateConcreteRef()
480+
heap.memcpy(ref, copyRef, arrayDescr.first, arrayDescr.second, mkBv(0), mkBv(0), mkBv(3))
481+
482+
val read = heap.readArrayIndex(copyRef, idx, arrayDescr.first, arrayDescr.second)
483+
assertSame(read, concreteValue)
484+
}
448485
}

0 commit comments

Comments
 (0)