Skip to content

Commit 7d0a6d5

Browse files
Abduqodiri Qurbonzodaqurbonzoda
authored andcommitted
PersistentOrderedSetIterator breaks if hash of an element changes #76
1 parent 0880e94 commit 7d0a6d5

File tree

5 files changed

+47
-3
lines changed

5 files changed

+47
-3
lines changed

core/commonMain/src/implementations/persistentOrderedMap/PersistentOrderedMapBuilderContentIterators.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ internal open class PersistentOrderedMapBuilderLinksIterator<K, V>(
2828
lastIteratedKey = nextKey
2929
nextWasInvoked = true
3030
index++
31-
val result = builder.hashMapBuilder[nextKey]!!
31+
@Suppress("UNCHECKED_CAST")
32+
val result = builder.hashMapBuilder.getOrElse(nextKey as K) {
33+
throw ConcurrentModificationException("Hash code of a key ($nextKey) has changed after it was added to the persistent map.")
34+
}
3235
nextKey = result.next
3336
return result
3437
}

core/commonMain/src/implementations/persistentOrderedMap/PersistentOrderedMapContentIterators.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ internal open class PersistentOrderedMapLinksIterator<K, V>(
2121
if (!hasNext()) {
2222
throw NoSuchElementException()
2323
}
24-
val result = hashMap[nextKey]!!
24+
@Suppress("UNCHECKED_CAST")
25+
val result = hashMap.getOrElse(nextKey as K) {
26+
throw ConcurrentModificationException("Hash code of a key ($nextKey) has changed after it was added to the persistent map.")
27+
}
2528
index++
2629
nextKey = result.next
2730
return result

core/commonMain/src/implementations/persistentOrderedSet/PersistentOrderedSetIterator.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ internal open class PersistentOrderedSetIterator<E>(private var nextElement: Any
1919
@Suppress("UNCHECKED_CAST")
2020
val result = nextElement as E
2121
index++
22-
nextElement = map[result]!!.next
22+
nextElement = map.getOrElse(result) {
23+
throw ConcurrentModificationException("Hash code of an element ($result) has changed after it was added to the persistent set.")
24+
}.next
2325
return result
2426
}
2527

core/commonTest/src/contract/map/ImmutableMapTest.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,24 @@ class ImmutableOrderedMapTest : ImmutableMapTest() {
3838
compare(listOf(1, 2), map.values) { collectionBehavior(ordered = true) }
3939
compare(mapOf("y" to 1, "x" to 2).entries, map.entries) { setBehavior(ordered = true) }
4040
}
41+
42+
@Test fun keyHashCodeChanged() {
43+
val changing = mutableSetOf("ok")
44+
val persistent: PersistentMap<Any, Any> = immutableMapOf("constant" to "fixed", changing to "modified")
45+
assertEquals(1, persistent.keys.filter { it === changing }.size)
46+
changing.add("break iteration")
47+
assertFailsWith<ConcurrentModificationException> { persistent.keys.filter { it === changing } }
48+
}
49+
50+
@Test fun builderKeyHashCodeChanged() {
51+
val changing = mutableSetOf("ok")
52+
val builder: PersistentMap.Builder<Any, Any> = immutableMapOf<Any, Any>().builder().apply {
53+
putAll(listOf("constant" to "fixed", changing to "modified"))
54+
}
55+
assertEquals(1, builder.filter { it.key === changing }.size)
56+
changing.add("break iteration")
57+
assertFailsWith<ConcurrentModificationException> { builder.filter { it.key === changing } }
58+
}
4159
}
4260

4361
abstract class ImmutableMapTest {

core/commonTest/src/contract/set/ImmutableSetTest.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,24 @@ class ImmutableOrderedSetTest : ImmutableSetTestBase() {
2424
override fun <T> testBuilderToPersistentSet(builder: PersistentSet.Builder<T>) {
2525
assertSame(builder.build(), builder.toPersistentSet(), "toPersistent should call build()")
2626
}
27+
28+
@Test fun elementHashCodeChanged() {
29+
val changing = mutableSetOf("ok")
30+
val persistent: PersistentSet<Any> = immutableSetOf("constant", changing, "fix")
31+
assertEquals(1, persistent.filter { it === changing }.size)
32+
changing.add("break iteration")
33+
assertFailsWith<ConcurrentModificationException> { persistent.filter { it === changing } }
34+
}
35+
36+
@Test fun builderElementHashCodeChanged() {
37+
val changing = mutableSetOf("ok")
38+
val builder: PersistentSet.Builder<Any> = immutableSetOf<Any>().builder().apply {
39+
addAll(listOf("constant", changing, "fix"))
40+
}
41+
assertEquals(1, builder.filter { it === changing }.size)
42+
changing.add("break iteration")
43+
assertFailsWith<ConcurrentModificationException> { builder.filter { it === changing } }
44+
}
2745
}
2846

2947
abstract class ImmutableSetTestBase {

0 commit comments

Comments
 (0)