@@ -35,13 +35,15 @@ import scala.runtime.Statics.releaseFence
3535 * @define mayNotTerminateInf
3636 * @define willNotTerminateInf
3737 */
38+ @ SerialVersionUID (- 8428291952499836345L )
3839class ListBuffer [A ]
3940 extends AbstractBuffer [A ]
4041 with SeqOps [A , ListBuffer , ListBuffer [A ]]
4142 with StrictOptimizedSeqOps [A , ListBuffer , ListBuffer [A ]]
4243 with ReusableBuilder [A , immutable.List [A ]]
4344 with IterableFactoryDefaults [A , ListBuffer ]
4445 with DefaultSerializable {
46+ @ transient private [this ] var mutationCount : Int = 0
4547
4648 private var first : List [A ] = Nil
4749 private var last0 : :: [A ] = null
@@ -50,7 +52,7 @@ class ListBuffer[A]
5052
5153 private type Predecessor [A0 ] = :: [A0 ] /* | Null*/
5254
53- def iterator = first.iterator
55+ def iterator : Iterator [ A ] = new MutationTracker . CheckedIterator ( first.iterator, mutationCount)
5456
5557 override def iterableFactory : SeqFactory [ListBuffer ] = ListBuffer
5658
@@ -69,7 +71,12 @@ class ListBuffer[A]
6971 aliased = false
7072 }
7173
72- private def ensureUnaliased () = if (aliased) copyElems()
74+ // we only call this before mutating things, so it's
75+ // a good place to track mutations for the iterator
76+ private def ensureUnaliased (): Unit = {
77+ mutationCount += 1
78+ if (aliased) copyElems()
79+ }
7380
7481 // Avoids copying where possible.
7582 override def toList : List [A ] = {
@@ -97,6 +104,7 @@ class ListBuffer[A]
97104 }
98105
99106 def clear (): Unit = {
107+ mutationCount += 1
100108 first = Nil
101109 len = 0
102110 last0 = null
@@ -114,18 +122,28 @@ class ListBuffer[A]
114122
115123 // Overridden for performance
116124 override final def addAll (xs : IterableOnce [A ]): this .type = {
117- val it = xs.iterator
118- if (it.hasNext) {
119- ensureUnaliased()
120- val last1 = new :: [A ](it.next(), Nil )
121- if (len == 0 ) first = last1 else last0.next = last1
122- last0 = last1
123- len += 1
124- while (it.hasNext) {
125+ if (xs.asInstanceOf [AnyRef ] eq this ) { // avoid mutating under our own iterator
126+ if (len > 0 ) {
127+ ensureUnaliased()
128+ val copy = ListBuffer .from(this )
129+ last0.next = copy.first
130+ last0 = copy.last0
131+ len *= 2
132+ }
133+ } else {
134+ val it = xs.iterator
135+ if (it.hasNext) {
136+ ensureUnaliased()
125137 val last1 = new :: [A ](it.next(), Nil )
126- last0.next = last1
138+ if (len == 0 ) first = last1 else last0.next = last1
127139 last0 = last1
128140 len += 1
141+ while (it.hasNext) {
142+ val last1 = new :: [A ](it.next(), Nil )
143+ last0.next = last1
144+ last0 = last1
145+ len += 1
146+ }
129147 }
130148 }
131149 this
@@ -230,13 +248,29 @@ class ListBuffer[A]
230248 }
231249
232250 def insertAll (idx : Int , elems : IterableOnce [A ]): Unit = {
233- ensureUnaliased()
234- val it = elems.iterator
235- if (it.hasNext) {
236- ensureUnaliased()
237- if (idx < 0 || idx > len) throw new IndexOutOfBoundsException (s " $idx is out of bounds (min 0, max ${len- 1 }) " )
238- if (idx == len) ++= (elems)
239- else insertAfter(locate(idx), it)
251+ if (idx < 0 || idx > len) throw new IndexOutOfBoundsException (s " $idx is out of bounds (min 0, max ${len- 1 }) " )
252+ elems match {
253+ case elems : AnyRef if elems eq this => // avoid mutating under our own iterator
254+ if (len > 0 ) {
255+ val copy = ListBuffer .from(this )
256+ if (idx == 0 || idx == len) { // prepend/append
257+ last0.next = copy.first
258+ last0 = copy.last0
259+ } else {
260+ val prev = locate(idx) // cannot be `null` because other condition catches that
261+ val follow = prev.next
262+ prev.next = copy.first
263+ copy.last0.next = follow
264+ }
265+ len *= 2
266+ }
267+ case elems =>
268+ val it = elems.iterator
269+ if (it.hasNext) {
270+ ensureUnaliased()
271+ if (idx == len) ++= (elems)
272+ else insertAfter(locate(idx), it)
273+ }
240274 }
241275 }
242276
@@ -275,15 +309,17 @@ class ListBuffer[A]
275309 }
276310
277311 def mapInPlace (f : A => A ): this .type = {
278- ensureUnaliased()
312+ mutationCount += 1
279313 val buf = new ListBuffer [A ]
280314 for (elem <- this ) buf += f(elem)
281315 first = buf.first
282316 last0 = buf.last0
317+ aliased = false // we just assigned from a new instance
283318 this
284319 }
285320
286321 def flatMapInPlace (f : A => IterableOnce [A ]): this .type = {
322+ mutationCount += 1
287323 var src = first
288324 var dst : List [A ] = null
289325 last0 = null
@@ -299,6 +335,7 @@ class ListBuffer[A]
299335 src = src.tail
300336 }
301337 first = if (dst eq null ) Nil else dst
338+ aliased = false // we just rebuilt a fresh, unaliased instance
302339 this
303340 }
304341
@@ -322,12 +359,24 @@ class ListBuffer[A]
322359 }
323360
324361 def patchInPlace (from : Int , patch : collection.IterableOnce [A ], replaced : Int ): this .type = {
325- val i = math.min(math.max(from, 0 ), length)
326- val n = math.min(math.max(replaced, 0 ), length)
327- ensureUnaliased()
328- val p = locate(i)
329- removeAfter(p, math.min(n, len - i))
330- insertAfter(p, patch.iterator)
362+ val _len = len
363+ val _from = math.max(from, 0 ) // normalized
364+ val _replaced = math.max(replaced, 0 ) // normalized
365+ val it = patch.iterator
366+
367+ val nonEmptyPatch = it.hasNext
368+ val nonEmptyReplace = (_from < _len) && (_replaced > 0 )
369+
370+ // don't want to add a mutation or check aliasing (potentially expensive)
371+ // if there's no patching to do
372+ if (nonEmptyPatch || nonEmptyReplace) {
373+ ensureUnaliased()
374+ val i = math.min(_from, _len)
375+ val n = math.min(_replaced, _len)
376+ val p = locate(i)
377+ removeAfter(p, math.min(n, _len - i))
378+ insertAfter(p, it)
379+ }
331380 this
332381 }
333382
0 commit comments