Skip to content

Commit 29a099b

Browse files
author
Abduqodiri Qurbonzoda
committed
Make PersistentHashMapBuilder iterators fail-fast
1 parent dd792b7 commit 29a099b

File tree

4 files changed

+55
-20
lines changed

4 files changed

+55
-20
lines changed

kotlinx-collections-immutable/src/main/kotlin/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@ internal class PersistentHashMapBuilder<K, V>(private var map: PersistentHashMap
2424
internal var marker = Marker()
2525
internal var node = map.node
2626
internal var operationResult: V? = null
27+
internal var modCount = 0
28+
29+
// Size change implies structural changes.
2730
override var size = map.size
31+
set(value) {
32+
field = value
33+
modCount++
34+
}
2835

2936
override fun build(): PersistentHashMap<K, V> {
3037
map = if (node === map.node) {

kotlinx-collections-immutable/src/main/kotlin/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilderContentIterators.kt

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,30 +43,31 @@ internal abstract class PersistentHashMapBuilderBaseIterator<K, V, T>(private va
4343
path: Array<TrieNodeBaseIterator<K, V, T>>)
4444
: MutableIterator<T>, PersistentHashMapBaseIterator<K, V, T>(builder.node, path) {
4545

46-
private var lastKey: K? = null
46+
private var lastIteratedKey: K? = null
4747
private var nextWasInvoked = false
48+
private var expectedModCount = builder.modCount
4849

4950
override fun next(): T {
50-
lastKey = currentKey()
51+
checkForComodification()
52+
lastIteratedKey = currentKey()
5153
nextWasInvoked = true
5254
return super.next()
5355
}
5456

5557
override fun remove() {
56-
if (!nextWasInvoked) {
57-
throw NoSuchElementException()
58-
}
58+
checkNextWasInvoked()
5959
if (hasNext()) {
6060
val currentKey = currentKey()
6161

62-
builder.remove(lastKey)
62+
builder.remove(lastIteratedKey)
6363
resetPath(currentKey.hashCode(), builder.node, currentKey, 0)
6464
} else {
65-
builder.remove(lastKey)
65+
builder.remove(lastIteratedKey)
6666
}
6767

68-
lastKey = null
68+
lastIteratedKey = null
6969
nextWasInvoked = false
70+
expectedModCount = builder.modCount
7071
}
7172

7273
private fun resetPath(keyHash: Int, node: TrieNode<*, *>, key: K, pathIndex: Int) {
@@ -76,13 +77,13 @@ internal abstract class PersistentHashMapBuilderBaseIterator<K, V, T>(private va
7677
if (node.hasDataAt(keyPosition)) { // key is directly in buffer
7778
val keyIndex = node.keyDataIndex(keyPosition)
7879

79-
assert(node.keyAtIndex(keyIndex) == key)
80+
// assert(node.keyAtIndex(keyIndex) == key)
8081

8182
path[pathIndex].reset(node.buffer, 2 * Integer.bitCount(node.dataMap), keyIndex)
8283
return
8384
}
8485

85-
assert(node.hasNodeAt(keyPosition)) // key is in node
86+
// assert(node.hasNodeAt(keyPosition)) // key is in node
8687

8788
val nodeIndex = node.keyNodeIndex(keyPosition)
8889
val targetNode = node.nodeAtIndex(nodeIndex)
@@ -96,6 +97,16 @@ internal abstract class PersistentHashMapBuilderBaseIterator<K, V, T>(private va
9697
resetPath(keyHash, targetNode, key, pathIndex + 1)
9798
}
9899
}
100+
101+
private fun checkNextWasInvoked() {
102+
if (!nextWasInvoked)
103+
throw IllegalStateException()
104+
}
105+
106+
private fun checkForComodification() {
107+
if (builder.modCount != expectedModCount)
108+
throw ConcurrentModificationException()
109+
}
99110
}
100111

101112
internal class PersistentHashMapBuilderEntriesIterator<K, V>(builder: PersistentHashMapBuilder<K, V>)

kotlinx-collections-immutable/src/main/kotlin/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapContentIterators.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ internal abstract class PersistentHashMapBaseIterator<K, V, T>(node: TrieNode<K,
134134
}
135135

136136
protected fun currentKey(): K {
137+
checkHasNext()
137138
return path[pathLastIndex].currentKey()
138139
}
139140

@@ -142,11 +143,16 @@ internal abstract class PersistentHashMapBaseIterator<K, V, T>(node: TrieNode<K,
142143
}
143144

144145
override fun next(): T {
145-
assert(hasNext())
146+
checkHasNext()
146147
val result = path[pathLastIndex].next()
147148
ensureNextEntryIsReady()
148149
return result
149150
}
151+
152+
private fun checkHasNext() {
153+
if (!hasNext())
154+
throw NoSuchElementException()
155+
}
150156
}
151157

152158
internal class PersistentHashMapEntriesIterator<K, V>(node: TrieNode<K, V>)

kotlinx-collections-immutable/src/main/kotlin/kotlinx/collections/immutable/implementations/immutableMap/TrieNode.kt

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ internal class TrieNode<K, V>(var dataMap: Int,
1919
return dataMap and position != 0
2020
}
2121

22-
internal fun hasNodeAt(position: Int): Boolean {
22+
private fun hasNodeAt(position: Int): Boolean {
2323
return nodeMap and position != 0
2424
}
2525

@@ -31,7 +31,7 @@ internal class TrieNode<K, V>(var dataMap: Int,
3131
return buffer.size - 1 - Integer.bitCount(nodeMap and (position - 1))
3232
}
3333

34-
internal fun keyAtIndex(keyIndex: Int): K {
34+
private fun keyAtIndex(keyIndex: Int): K {
3535
return buffer[keyIndex] as K
3636
}
3737

@@ -84,11 +84,14 @@ internal class TrieNode<K, V>(var dataMap: Int,
8484
private fun mutableUpdateValueAtIndex(keyIndex: Int, value: V, mutator: PersistentHashMapBuilder<K, V>): TrieNode<K, V> {
8585
// assert(buffer[keyIndex + 1] !== value)
8686

87-
mutator.operationResult = buffer[keyIndex + 1] as V
87+
// If the [mutator] is exclusive owner of this node, update value at specified index in-place.
8888
if (marker === mutator.marker) {
8989
buffer[keyIndex + 1] = value
9090
return this
9191
}
92+
// Structural change due to node replacement.
93+
mutator.modCount++
94+
// Create new node with updated value at specified index.
9295
val newBuffer = buffer.copyOf()
9396
newBuffer[keyIndex + 1] = value
9497
return TrieNode(dataMap, nodeMap, newBuffer, mutator.marker)
@@ -103,6 +106,8 @@ internal class TrieNode<K, V>(var dataMap: Int,
103106
}
104107

105108
private fun mutableUpdateNodeAtIndex(nodeIndex: Int, newNode: TrieNode<K, V>, mutatorMarker: Marker): TrieNode<K, V> {
109+
// assert(buffer[nodeIndex] !== newNode)
110+
106111
if (marker === mutatorMarker) {
107112
buffer[nodeIndex] = newNode
108113
return this
@@ -286,19 +291,26 @@ internal class TrieNode<K, V>(var dataMap: Int,
286291
}
287292

288293
private fun mutableCollisionPut(key: K, value: V, mutator: PersistentHashMapBuilder<K, V>): TrieNode<K, V> {
294+
// Check if there is an entry with the specified key.
289295
for (i in 0 until buffer.size step ENTRY_SIZE) {
290-
if (key == buffer[i]) {
296+
if (key == buffer[i]) { // found entry with the specified key
291297
mutator.operationResult = buffer[i + 1] as V
292298

299+
// If the [mutator] is exclusive owner of this node, update value of the entry in-place.
293300
if (marker === mutator.marker) {
294301
buffer[i + 1] = value
295302
return this
296303
}
304+
305+
// Structural change due to node replacement.
306+
mutator.modCount++
307+
// Create new node with updated entry value.
297308
val newBuffer = buffer.copyOf()
298309
newBuffer[i + 1] = value
299310
return TrieNode(0, 0, newBuffer, mutator.marker)
300311
}
301312
}
313+
// Create new collision node with the specified entry added to it.
302314
mutator.size++
303315
val newBuffer = bufferPutDataAtIndex(0, key, value)
304316
return TrieNode(0, 0, newBuffer, mutator.marker)
@@ -388,9 +400,9 @@ internal class TrieNode<K, V>(var dataMap: Int,
388400
val keyIndex = keyDataIndex(keyPosition)
389401

390402
if (key == keyAtIndex(keyIndex)) {
403+
modification.value = UPDATE_VALUE
391404
if (valueAtKeyIndex(keyIndex) === value) { return this }
392405

393-
modification.value = UPDATE_VALUE
394406
return updateValueAtIndex(keyIndex, value)
395407
}
396408
modification.value = PUT_KEY_VALUE
@@ -421,10 +433,9 @@ internal class TrieNode<K, V>(var dataMap: Int,
421433
val keyIndex = keyDataIndex(keyPosition)
422434

423435
if (key == keyAtIndex(keyIndex)) {
424-
if (valueAtKeyIndex(keyIndex) === value) { // needs discussion
425-
mutator.operationResult = value
426-
return this
427-
}
436+
mutator.operationResult = valueAtKeyIndex(keyIndex)
437+
if (valueAtKeyIndex(keyIndex) === value) { return this }
438+
428439
return mutableUpdateValueAtIndex(keyIndex, value, mutator)
429440
}
430441
mutator.size++

0 commit comments

Comments
 (0)