Skip to content

Commit 5a79c03

Browse files
committed
Change ListIterator to only check for concurrent modification at each iteration
in checked mode. It also checks at the end in all cases. Iteration only goes from 0 to the original length of the list. This ensures that iterating a list while adding to it (like by x.addAll(x)) is caught instead of growing until out-of-memory. For well-behaved programs this makes no difference since length and original length stay the same. Also, it means that calling moveNext again later, after increasing the length, will not make iteration continue. After returning false, iteration is always done. However, it means that reducing the length causes an out-of-range read before reaching the end, and before a concurrent modification error can happen. [email protected] Review URL: https://codereview.chromium.org//1024843002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@45198 260f80e4-7a28-3924-810f-c04153c831b5
1 parent d26580a commit 5a79c03

File tree

7 files changed

+55
-37
lines changed

7 files changed

+55
-37
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
### Core library changes
44

5+
* List iterators may not throw ConcurrentModificationError as eagerly in
6+
release mode. In checked mode, the modification check is still as eager
7+
as possible.
8+
[r45198](https://code.google.com/p/dart/source/detail?r=45198),
59
* Update experimental Isolate API:
610
- Make priorty parameters of `Isolate.ping` and `Isolate.kill` methods
711
a named parameter.

sdk/lib/_internal/compiler/js_lib/js_array.dart

+14-17
Original file line numberDiff line numberDiff line change
@@ -605,34 +605,31 @@ class JSExtendableArray<E> extends JSMutableArray<E> {}
605605

606606

607607
/// An [Iterator] that iterates a JSArray.
608-
///
609608
class ArrayIterator<E> implements Iterator<E> {
610609
final JSArray<E> _iterable;
611-
final int _length;
610+
final int _originalLength;
612611
int _index;
613612
E _current;
614613

615614
ArrayIterator(JSArray<E> iterable)
616-
: _iterable = iterable, _length = iterable.length, _index = 0;
615+
: _iterable = iterable, _originalLength = iterable.length, _index = 0;
617616

618617
E get current => _current;
619618

620619
bool moveNext() {
621-
int length = _iterable.length;
622-
623-
// We have to do the length check even on fixed length Arrays. If we can
624-
// inline moveNext() we might be able to GVN the length and eliminate this
625-
// check on known fixed length JSArray.
626-
if (_length != length) {
620+
// Check for concurrent modifiction at each step in checked mode.
621+
assert((_originalLength == _iterable.length) ||
622+
(throw new ConcurrentModificationError(_iterable)));
623+
if (_index < _iterable.length) {
624+
_current = _iterable.elementAt(_index);
625+
_index++;
626+
return true;
627+
}
628+
// Check for concurrent modification only at the end in production mode.
629+
if (_originalLength != _iterable.length) {
627630
throw new ConcurrentModificationError(_iterable);
628631
}
629-
630-
if (_index >= length) {
631-
_current = null;
632-
return false;
633-
}
634-
_current = _iterable[_index];
635-
_index++;
636-
return true;
632+
_current = null;
633+
return false;
637634
}
638635
}

sdk/lib/internal/iterable.dart

+14-11
Original file line numberDiff line numberDiff line change
@@ -320,27 +320,30 @@ class SubListIterable<E> extends ListIterable<E> {
320320
*/
321321
class ListIterator<E> implements Iterator<E> {
322322
final Iterable<E> _iterable;
323-
final int _length;
323+
final int _originalLength;
324324
int _index;
325325
E _current;
326326

327327
ListIterator(Iterable<E> iterable)
328-
: _iterable = iterable, _length = iterable.length, _index = 0;
328+
: _iterable = iterable, _originalLength = iterable.length, _index = 0;
329329

330330
E get current => _current;
331331

332332
bool moveNext() {
333-
int length = _iterable.length;
334-
if (_length != length) {
335-
throw new ConcurrentModificationError(_iterable);
333+
// Check for concurrent modifiction at each step in checked mode.
334+
assert((_originalLength == _iterable.length) ||
335+
(throw new ConcurrentModificationError(_iterable)));
336+
if (_index < _iterable.length) {
337+
_current = _iterable.elementAt(_index);
338+
_index++;
339+
return true;
336340
}
337-
if (_index >= length) {
338-
_current = null;
339-
return false;
341+
// Check for concurrent modification only at the end in production mode.
342+
if (_originalLength != _iterable.length) {
343+
throw new ConcurrentModificationError(_iterable);
340344
}
341-
_current = _iterable.elementAt(_index);
342-
_index++;
343-
return true;
345+
_current = null;
346+
return false;
344347
}
345348
}
346349

tests/corelib/iterable_fold_test.dart

+3-2
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ main() {
123123
}, (e) => e is ConcurrentModificationError);
124124
}
125125

126-
void add4(collection) { collection.add(4); }
126+
void add4(collection) { if (collection.length < 10) collection.add(4); }
127+
void addList4(collection) { if (collection.length < 10) collection.add([4]); }
127128
void put4(map) { map[4] = 4; }
128129

129130
testModification([1, 2, 3], add4, id);
@@ -134,7 +135,7 @@ main() {
134135

135136
testModification([0, 1, 2, 3], add4, (x) => x.where((x) => x > 0));
136137
testModification([0, 1, 2], add4, (x) => x.map((x) => x + 1));
137-
testModification([[1, 2], [3]], add4, (x) => x.expand((x) => x));
138+
testModification([[1, 2], [3]], addList4, (x) => x.expand((x) => x));
138139
testModification([3, 2, 1], add4, (x) => x.reversed);
139140
testModification({1: 1, 2: 2, 3: 3}, put4, (x) => x.keys);
140141
testModification({1: 1, 2: 2, 3: 3}, put4, (x) => x.values);

tests/corelib/iterable_reduce_test.dart

+4-2
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,9 @@ main() {
125125
}, (e) => e is ConcurrentModificationError);
126126
}
127127

128-
void add4(collection) { collection.add(4); }
128+
// Add elements, but not infinitely often.
129+
void add4(collection) { if (collection.length < 10) collection.add(4); }
130+
void addList4(collection) { if (collection.length < 10) collection.add([4]); }
129131
void put4(map) { map[4] = 4; }
130132

131133
testModification([1, 2, 3], add4, id);
@@ -136,7 +138,7 @@ main() {
136138

137139
testModification([0, 1, 2, 3], add4, (x) => x.where((x) => x > 0));
138140
testModification([0, 1, 2], add4, (x) => x.map((x) => x + 1));
139-
testModification([[1, 2], [3]], add4, (x) => x.expand((x) => x));
141+
testModification([[1, 2], [3]], addList4, (x) => x.expand((x) => x));
140142
testModification([3, 2, 1], add4, (x) => x.reversed);
141143
testModification({1: 1, 2: 2, 3: 3}, put4, (x) => x.keys);
142144
testModification({1: 1, 2: 2, 3: 3}, put4, (x) => x.values);

tests/corelib/json_map_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ void testConcurrentModifications() {
162162
void testIterate(Map map, Iterable iterable, Function f) {
163163
Iterator iterator = iterable.iterator;
164164
f(map);
165-
iterator.moveNext();
165+
while (iterator.moveNext());
166166
}
167167

168168
void testKeys(Map map, Function f) => testIterate(map, map.keys, f);

tests/corelib/list_test.dart

+15-4
Original file line numberDiff line numberDiff line change
@@ -376,23 +376,34 @@ void testGrowableListOperations(List list) {
376376
list.replaceRange(6, 8, []);
377377
Expect.listEquals([1, 2, 6, 6, 5, 0, 2, 3, 2, 1], list);
378378

379-
// Operations that change the length cause ConcurrentModificationError.
379+
// Operations that change the length may cause ConcurrentModificationError.
380+
// Reducing the length may cause a RangeError before the
381+
// ConcurrentModificationError in production mode.
382+
bool checkedMode = false;
383+
assert((checkedMode = true));
384+
380385
void testConcurrentModification(action()) {
381386
testIterator(int when) {
382387
list.length = 4;
383388
list.setAll(0, [0, 1, 2, 3]);
384389
Expect.throws(() {
385390
for (var element in list) {
386-
if (element == when) action();
391+
if (element == when) {
392+
when = -1; // Prevent triggering more than once.
393+
action();
394+
}
387395
}
388-
}, (e) => e is ConcurrentModificationError);
396+
}, (e) => !checkedMode || e is ConcurrentModificationError);
389397
}
390398
testForEach(int when) {
391399
list.length = 4;
392400
list.setAll(0, [0, 1, 2, 3]);
393401
Expect.throws(() {
394402
list.forEach((var element) {
395-
if (element == when) action();
403+
if (element == when) {
404+
when = -1;
405+
action();
406+
}
396407
});
397408
}, (e) => e is ConcurrentModificationError);
398409
}

0 commit comments

Comments
 (0)