Fix ArrayDeque's bug when expanding capacity.
Also fixes concurrent modification check for the case
when head > tail.
Bug: #9404
Bug-Link: https://github.com/gwtproject/gwt/issues/9404
Change-Id: I9e13b1c80ddad1c69d8863855d2f4f6fb62b1b6f
diff --git a/user/super/com/google/gwt/emul/java/util/ArrayDeque.java b/user/super/com/google/gwt/emul/java/util/ArrayDeque.java
index 619b7e8..694593f 100644
--- a/user/super/com/google/gwt/emul/java/util/ArrayDeque.java
+++ b/user/super/com/google/gwt/emul/java/util/ArrayDeque.java
@@ -72,7 +72,6 @@
@Override
public void remove() {
checkState(lastIndex >= 0);
- checkConcurrentModification(head <= lastIndex && lastIndex < tail);
if (removeAtIndex(lastIndex) < 0) {
// if left-shifted, undo increment in next()
@@ -107,7 +106,6 @@
@Override
public void remove() {
checkState(lastIndex >= 0);
- checkConcurrentModification(head <= lastIndex && lastIndex < tail);
if (removeAtIndex(lastIndex) > 0) {
// if right-shifted, undo decrement in next()
@@ -382,11 +380,7 @@
if (out.length < size) {
out = ArrayHelper.createFrom(out, size);
}
- Object[] dest = out;
- final int mask = size - 1;
- for (int i = head, dstIdx = 0; dstIdx < size; i = (i + 1) & mask, ++dstIdx) {
- dest[dstIdx] = array[i];
- }
+ copyElements(out, size);
if (out.length > size) {
out[size] = null;
}
@@ -423,6 +417,19 @@
}
/**
+ * Copies {@code count} ArrayDeque's elements to {@code dest} array.
+ * The method is safe to use when ArrayDeque's array has been rolled over,
+ * i.e. {@code head == tail}.
+ * It is assumed that {@code count < size()}.
+ */
+ private void copyElements(Object[] dest, int count) {
+ final int mask = array.length - 1;
+ for (int i = head, dstIdx = 0; dstIdx < count; i = (i + 1) & mask, ++dstIdx) {
+ dest[dstIdx] = array[i];
+ }
+ }
+
+ /**
* Increase the capacity of this deque when full, i.e.,
* when head and tail have wrapped around to become equal.
*/
@@ -435,11 +442,12 @@
int newLength = nextArrayLength(numElements);
if (head != 0) {
E[] newArray = ArrayHelper.createFrom(array, newLength);
- array = toArray(newArray);
+ copyElements(newArray, numElements);
+ array = newArray;
+ head = 0;
} else {
ArrayHelper.setLength(array, newLength);
}
- head = 0;
tail = numElements;
}
@@ -454,6 +462,9 @@
final int mask = array.length - 1;
int headDistance = (i - head) & mask;
int tailDistance = (tail - i) & mask;
+ int size = (tail - head) & mask;
+
+ checkConcurrentModification(headDistance < size);
if (headDistance >= tailDistance) {
shiftLeftAtIndex(i);
return -1;
diff --git a/user/test/com/google/gwt/emultest/java/util/ArrayDequeTest.java b/user/test/com/google/gwt/emultest/java/util/ArrayDequeTest.java
index 9915e2a..13e5a09 100644
--- a/user/test/com/google/gwt/emultest/java/util/ArrayDequeTest.java
+++ b/user/test/com/google/gwt/emultest/java/util/ArrayDequeTest.java
@@ -822,6 +822,28 @@
assertFalse(deque.removeLastOccurrence(null));
}
+ public void testRolloverInvariants() {
+ ArrayDeque<Integer> deque = new ArrayDeque<>();
+
+ assertTrue(deque.add(1));
+ assertEquals(1, (int) deque.removeFirst());
+ for (int i = 0; i < 100; i++) {
+ assertTrue(deque.add(i));
+ }
+ assertNotNull(deque.peek());
+ assertFalse(deque.isEmpty());
+
+ Iterator<Integer> it = deque.iterator();
+ for (int i = 0; i < 100; i++) {
+ assertTrue(it.hasNext());
+ assertEquals(i, (int) it.next());
+ it.remove();
+ }
+ assertFalse(it.hasNext());
+ assertNull(deque.peek());
+ assertTrue(deque.isEmpty());
+ }
+
/**
* Null elements are prohibited in ArrayDeque.
*/