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.

R=sra@google.com

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
This commit is contained in:
lrn@google.com 2015-04-16 09:07:58 +00:00
parent d26580a98b
commit 5a79c03e09
7 changed files with 55 additions and 37 deletions

View file

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

View file

@ -605,34 +605,31 @@ class JSExtendableArray<E> extends JSMutableArray<E> {}
/// An [Iterator] that iterates a JSArray. /// An [Iterator] that iterates a JSArray.
///
class ArrayIterator<E> implements Iterator<E> { class ArrayIterator<E> implements Iterator<E> {
final JSArray<E> _iterable; final JSArray<E> _iterable;
final int _length; final int _originalLength;
int _index; int _index;
E _current; E _current;
ArrayIterator(JSArray<E> iterable) ArrayIterator(JSArray<E> iterable)
: _iterable = iterable, _length = iterable.length, _index = 0; : _iterable = iterable, _originalLength = iterable.length, _index = 0;
E get current => _current; E get current => _current;
bool moveNext() { bool moveNext() {
int length = _iterable.length; // Check for concurrent modifiction at each step in checked mode.
assert((_originalLength == _iterable.length) ||
// We have to do the length check even on fixed length Arrays. If we can (throw new ConcurrentModificationError(_iterable)));
// inline moveNext() we might be able to GVN the length and eliminate this if (_index < _iterable.length) {
// check on known fixed length JSArray. _current = _iterable.elementAt(_index);
if (_length != length) { _index++;
return true;
}
// Check for concurrent modification only at the end in production mode.
if (_originalLength != _iterable.length) {
throw new ConcurrentModificationError(_iterable); throw new ConcurrentModificationError(_iterable);
} }
_current = null;
if (_index >= length) { return false;
_current = null;
return false;
}
_current = _iterable[_index];
_index++;
return true;
} }
} }

View file

@ -320,27 +320,30 @@ class SubListIterable<E> extends ListIterable<E> {
*/ */
class ListIterator<E> implements Iterator<E> { class ListIterator<E> implements Iterator<E> {
final Iterable<E> _iterable; final Iterable<E> _iterable;
final int _length; final int _originalLength;
int _index; int _index;
E _current; E _current;
ListIterator(Iterable<E> iterable) ListIterator(Iterable<E> iterable)
: _iterable = iterable, _length = iterable.length, _index = 0; : _iterable = iterable, _originalLength = iterable.length, _index = 0;
E get current => _current; E get current => _current;
bool moveNext() { bool moveNext() {
int length = _iterable.length; // Check for concurrent modifiction at each step in checked mode.
if (_length != length) { assert((_originalLength == _iterable.length) ||
(throw new ConcurrentModificationError(_iterable)));
if (_index < _iterable.length) {
_current = _iterable.elementAt(_index);
_index++;
return true;
}
// Check for concurrent modification only at the end in production mode.
if (_originalLength != _iterable.length) {
throw new ConcurrentModificationError(_iterable); throw new ConcurrentModificationError(_iterable);
} }
if (_index >= length) { _current = null;
_current = null; return false;
return false;
}
_current = _iterable.elementAt(_index);
_index++;
return true;
} }
} }

View file

@ -123,7 +123,8 @@ main() {
}, (e) => e is ConcurrentModificationError); }, (e) => e is ConcurrentModificationError);
} }
void add4(collection) { collection.add(4); } void add4(collection) { if (collection.length < 10) collection.add(4); }
void addList4(collection) { if (collection.length < 10) collection.add([4]); }
void put4(map) { map[4] = 4; } void put4(map) { map[4] = 4; }
testModification([1, 2, 3], add4, id); testModification([1, 2, 3], add4, id);
@ -134,7 +135,7 @@ main() {
testModification([0, 1, 2, 3], add4, (x) => x.where((x) => x > 0)); testModification([0, 1, 2, 3], add4, (x) => x.where((x) => x > 0));
testModification([0, 1, 2], add4, (x) => x.map((x) => x + 1)); testModification([0, 1, 2], add4, (x) => x.map((x) => x + 1));
testModification([[1, 2], [3]], add4, (x) => x.expand((x) => x)); testModification([[1, 2], [3]], addList4, (x) => x.expand((x) => x));
testModification([3, 2, 1], add4, (x) => x.reversed); testModification([3, 2, 1], add4, (x) => x.reversed);
testModification({1: 1, 2: 2, 3: 3}, put4, (x) => x.keys); testModification({1: 1, 2: 2, 3: 3}, put4, (x) => x.keys);
testModification({1: 1, 2: 2, 3: 3}, put4, (x) => x.values); testModification({1: 1, 2: 2, 3: 3}, put4, (x) => x.values);

View file

@ -125,7 +125,9 @@ main() {
}, (e) => e is ConcurrentModificationError); }, (e) => e is ConcurrentModificationError);
} }
void add4(collection) { collection.add(4); } // Add elements, but not infinitely often.
void add4(collection) { if (collection.length < 10) collection.add(4); }
void addList4(collection) { if (collection.length < 10) collection.add([4]); }
void put4(map) { map[4] = 4; } void put4(map) { map[4] = 4; }
testModification([1, 2, 3], add4, id); testModification([1, 2, 3], add4, id);
@ -136,7 +138,7 @@ main() {
testModification([0, 1, 2, 3], add4, (x) => x.where((x) => x > 0)); testModification([0, 1, 2, 3], add4, (x) => x.where((x) => x > 0));
testModification([0, 1, 2], add4, (x) => x.map((x) => x + 1)); testModification([0, 1, 2], add4, (x) => x.map((x) => x + 1));
testModification([[1, 2], [3]], add4, (x) => x.expand((x) => x)); testModification([[1, 2], [3]], addList4, (x) => x.expand((x) => x));
testModification([3, 2, 1], add4, (x) => x.reversed); testModification([3, 2, 1], add4, (x) => x.reversed);
testModification({1: 1, 2: 2, 3: 3}, put4, (x) => x.keys); testModification({1: 1, 2: 2, 3: 3}, put4, (x) => x.keys);
testModification({1: 1, 2: 2, 3: 3}, put4, (x) => x.values); testModification({1: 1, 2: 2, 3: 3}, put4, (x) => x.values);

View file

@ -162,7 +162,7 @@ void testConcurrentModifications() {
void testIterate(Map map, Iterable iterable, Function f) { void testIterate(Map map, Iterable iterable, Function f) {
Iterator iterator = iterable.iterator; Iterator iterator = iterable.iterator;
f(map); f(map);
iterator.moveNext(); while (iterator.moveNext());
} }
void testKeys(Map map, Function f) => testIterate(map, map.keys, f); void testKeys(Map map, Function f) => testIterate(map, map.keys, f);

View file

@ -376,23 +376,34 @@ void testGrowableListOperations(List list) {
list.replaceRange(6, 8, []); list.replaceRange(6, 8, []);
Expect.listEquals([1, 2, 6, 6, 5, 0, 2, 3, 2, 1], list); Expect.listEquals([1, 2, 6, 6, 5, 0, 2, 3, 2, 1], list);
// Operations that change the length cause ConcurrentModificationError. // Operations that change the length may cause ConcurrentModificationError.
// Reducing the length may cause a RangeError before the
// ConcurrentModificationError in production mode.
bool checkedMode = false;
assert((checkedMode = true));
void testConcurrentModification(action()) { void testConcurrentModification(action()) {
testIterator(int when) { testIterator(int when) {
list.length = 4; list.length = 4;
list.setAll(0, [0, 1, 2, 3]); list.setAll(0, [0, 1, 2, 3]);
Expect.throws(() { Expect.throws(() {
for (var element in list) { for (var element in list) {
if (element == when) action(); if (element == when) {
when = -1; // Prevent triggering more than once.
action();
}
} }
}, (e) => e is ConcurrentModificationError); }, (e) => !checkedMode || e is ConcurrentModificationError);
} }
testForEach(int when) { testForEach(int when) {
list.length = 4; list.length = 4;
list.setAll(0, [0, 1, 2, 3]); list.setAll(0, [0, 1, 2, 3]);
Expect.throws(() { Expect.throws(() {
list.forEach((var element) { list.forEach((var element) {
if (element == when) action(); if (element == when) {
when = -1;
action();
}
}); });
}, (e) => e is ConcurrentModificationError); }, (e) => e is ConcurrentModificationError);
} }