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. */