# HG changeset patch # User igerasim # Date 1430941161 -10800 # Node ID a9380dea5aa0932e8537985eb5179844f502d1ea # Parent 51f5501a54a62aceed6342e7ea3a9a3c67f3885d [mq]: 8079136-NestedSubList diff --git a/src/java.base/share/classes/java/util/AbstractList.java b/src/java.base/share/classes/java/util/AbstractList.java --- a/src/java.base/share/classes/java/util/AbstractList.java +++ b/src/java.base/share/classes/java/util/AbstractList.java @@ -493,11 +493,22 @@ * {@code (fromIndex > toIndex)} */ public List subList(int fromIndex, int toIndex) { + subListRangeCheck(fromIndex, toIndex, size()); return (this instanceof RandomAccess ? new RandomAccessSubList<>(this, fromIndex, toIndex) : new SubList<>(this, fromIndex, toIndex)); } + static void subListRangeCheck(int fromIndex, int toIndex, int size) { + if (fromIndex < 0) + throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); + if (toIndex > size) + throw new IndexOutOfBoundsException("toIndex = " + toIndex); + if (fromIndex > toIndex) + throw new IllegalArgumentException("fromIndex(" + fromIndex + + ") > toIndex(" + toIndex + ")"); + } + // Comparison and hashing /** @@ -621,39 +632,49 @@ } private String outOfBoundsMsg(int index) { - return "Index: "+index+", Size: "+size(); + return "Index: " + index + ", Size: " + size(); } } class SubList extends AbstractList { - private final AbstractList l; - private final int offset; - private int size; + final AbstractList root; + final SubList parent; + final int offset; + int size; - SubList(AbstractList list, int fromIndex, int toIndex) { - if (fromIndex < 0) - throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); - if (toIndex > list.size()) - throw new IndexOutOfBoundsException("toIndex = " + toIndex); - if (fromIndex > toIndex) - throw new IllegalArgumentException("fromIndex(" + fromIndex + - ") > toIndex(" + toIndex + ")"); - l = list; - offset = fromIndex; - size = toIndex - fromIndex; - this.modCount = l.modCount; + /** + * Constructs a sublist of an arbitrary AbstractList, which is + * not a SubList itself. + */ + public SubList(AbstractList root, int fromIndex, int toIndex) { + this.root = root; + this.parent = null; + this.offset = fromIndex; + this.size = toIndex - fromIndex; + this.modCount = root.modCount; + } + + /** + * Constructs a sublist of another SubList. + */ + protected SubList(SubList parent, int fromIndex, int toIndex) { + this.root = parent.root; + this.parent = parent; + this.offset = parent.offset + fromIndex; + this.size = toIndex - fromIndex; + this.modCount = root.modCount; } public E set(int index, E element) { rangeCheck(index); checkForComodification(); - return l.set(index+offset, element); + return root.set(offset + index, element); } public E get(int index) { rangeCheck(index); checkForComodification(); - return l.get(index+offset); + return root.get(offset + index); } public int size() { @@ -664,25 +685,22 @@ public void add(int index, E element) { rangeCheckForAdd(index); checkForComodification(); - l.add(index+offset, element); - this.modCount = l.modCount; - size++; + root.add(offset + index, element); + updateSizeAndModCount(1); } public E remove(int index) { rangeCheck(index); checkForComodification(); - E result = l.remove(index+offset); - this.modCount = l.modCount; - size--; + E result = root.remove(offset + index); + updateSizeAndModCount(-1); return result; } protected void removeRange(int fromIndex, int toIndex) { checkForComodification(); - l.removeRange(fromIndex+offset, toIndex+offset); - this.modCount = l.modCount; - size -= (toIndex-fromIndex); + root.removeRange(offset + fromIndex, offset + toIndex); + updateSizeAndModCount(fromIndex - toIndex); } public boolean addAll(Collection c) { @@ -692,13 +710,11 @@ public boolean addAll(int index, Collection c) { rangeCheckForAdd(index); int cSize = c.size(); - if (cSize==0) + if (cSize == 0) return false; - checkForComodification(); - l.addAll(offset+index, c); - this.modCount = l.modCount; - size += cSize; + root.addAll(offset + index, c); + updateSizeAndModCount(cSize); return true; } @@ -706,12 +722,13 @@ return listIterator(); } - public ListIterator listIterator(final int index) { + public ListIterator listIterator(int index) { checkForComodification(); rangeCheckForAdd(index); return new ListIterator() { - private final ListIterator i = l.listIterator(index+offset); + private final ListIterator i = + root.listIterator(offset + index); public boolean hasNext() { return nextIndex() < size; @@ -745,8 +762,7 @@ public void remove() { i.remove(); - SubList.this.modCount = l.modCount; - size--; + updateSizeAndModCount(-1); } public void set(E e) { @@ -755,13 +771,13 @@ public void add(E e) { i.add(e); - SubList.this.modCount = l.modCount; - size++; + updateSizeAndModCount(1); } }; } public List subList(int fromIndex, int toIndex) { + subListRangeCheck(fromIndex, toIndex, size); return new SubList<>(this, fromIndex, toIndex); } @@ -776,21 +792,46 @@ } private String outOfBoundsMsg(int index) { - return "Index: "+index+", Size: "+size; + return "Index: " + index + ", Size: " + size; } private void checkForComodification() { - if (this.modCount != l.modCount) + if (root.modCount != this.modCount) throw new ConcurrentModificationException(); } + + private void updateSizeAndModCount(int sizeChange) { + SubList slist = this; + do { + slist.size += sizeChange; + slist.modCount = root.modCount; + slist = slist.parent; + } while (slist != null); + } } -class RandomAccessSubList extends SubList implements RandomAccess { - RandomAccessSubList(AbstractList list, int fromIndex, int toIndex) { - super(list, fromIndex, toIndex); +class RandomAccessSubList extends SubList + implements RandomAccess { + + /** + * Constructs a sublist of an arbitrary AbstractList, which is + * not a RandomAccessSubList itself. + */ + RandomAccessSubList(AbstractList root, + int fromIndex, int toIndex) { + super(root, fromIndex, toIndex); + } + + /** + * Constructs a sublist of another RandomAccessSubList. + */ + RandomAccessSubList(RandomAccessSubList parent, + int fromIndex, int toIndex) { + super(parent, fromIndex, toIndex); } public List subList(int fromIndex, int toIndex) { + subListRangeCheck(fromIndex, toIndex, size); return new RandomAccessSubList<>(this, fromIndex, toIndex); } } diff --git a/src/java.base/share/classes/java/util/ArrayList.java b/src/java.base/share/classes/java/util/ArrayList.java --- a/src/java.base/share/classes/java/util/ArrayList.java +++ b/src/java.base/share/classes/java/util/ArrayList.java @@ -672,7 +672,7 @@ * this "outlining" performs best with both server and client VMs. */ private String outOfBoundsMsg(int index) { - return "Index: "+index+", Size: "+size; + return "Index: " + index + ", Size: " + size; } /** @@ -1006,248 +1006,7 @@ */ public List subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); - return new SubList(this, 0, fromIndex, toIndex); - } - - static void subListRangeCheck(int fromIndex, int toIndex, int size) { - if (fromIndex < 0) - throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); - if (toIndex > size) - throw new IndexOutOfBoundsException("toIndex = " + toIndex); - if (fromIndex > toIndex) - throw new IllegalArgumentException("fromIndex(" + fromIndex + - ") > toIndex(" + toIndex + ")"); - } - - private class SubList extends AbstractList implements RandomAccess { - private final AbstractList parent; - private final int parentOffset; - private final int offset; - int size; - - SubList(AbstractList parent, - int offset, int fromIndex, int toIndex) { - this.parent = parent; - this.parentOffset = fromIndex; - this.offset = offset + fromIndex; - this.size = toIndex - fromIndex; - this.modCount = ArrayList.this.modCount; - } - - public E set(int index, E e) { - rangeCheck(index); - checkForComodification(); - E oldValue = ArrayList.this.elementData(offset + index); - ArrayList.this.elementData[offset + index] = e; - return oldValue; - } - - public E get(int index) { - rangeCheck(index); - checkForComodification(); - return ArrayList.this.elementData(offset + index); - } - - public int size() { - checkForComodification(); - return this.size; - } - - public void add(int index, E e) { - rangeCheckForAdd(index); - checkForComodification(); - parent.add(parentOffset + index, e); - this.modCount = parent.modCount; - this.size++; - } - - public E remove(int index) { - rangeCheck(index); - checkForComodification(); - E result = parent.remove(parentOffset + index); - this.modCount = parent.modCount; - this.size--; - return result; - } - - protected void removeRange(int fromIndex, int toIndex) { - checkForComodification(); - parent.removeRange(parentOffset + fromIndex, - parentOffset + toIndex); - this.modCount = parent.modCount; - this.size -= toIndex - fromIndex; - } - - public boolean addAll(Collection c) { - return addAll(this.size, c); - } - - public boolean addAll(int index, Collection c) { - rangeCheckForAdd(index); - int cSize = c.size(); - if (cSize==0) - return false; - - checkForComodification(); - parent.addAll(parentOffset + index, c); - this.modCount = parent.modCount; - this.size += cSize; - return true; - } - - public Iterator iterator() { - return listIterator(); - } - - public ListIterator listIterator(final int index) { - checkForComodification(); - rangeCheckForAdd(index); - final int offset = this.offset; - - return new ListIterator() { - int cursor = index; - int lastRet = -1; - int expectedModCount = ArrayList.this.modCount; - - public boolean hasNext() { - return cursor != SubList.this.size; - } - - @SuppressWarnings("unchecked") - public E next() { - checkForComodification(); - int i = cursor; - if (i >= SubList.this.size) - throw new NoSuchElementException(); - Object[] elementData = ArrayList.this.elementData; - if (offset + i >= elementData.length) - throw new ConcurrentModificationException(); - cursor = i + 1; - return (E) elementData[offset + (lastRet = i)]; - } - - public boolean hasPrevious() { - return cursor != 0; - } - - @SuppressWarnings("unchecked") - public E previous() { - checkForComodification(); - int i = cursor - 1; - if (i < 0) - throw new NoSuchElementException(); - Object[] elementData = ArrayList.this.elementData; - if (offset + i >= elementData.length) - throw new ConcurrentModificationException(); - cursor = i; - return (E) elementData[offset + (lastRet = i)]; - } - - @SuppressWarnings("unchecked") - public void forEachRemaining(Consumer consumer) { - Objects.requireNonNull(consumer); - final int size = SubList.this.size; - int i = cursor; - if (i >= size) { - return; - } - final Object[] elementData = ArrayList.this.elementData; - if (offset + i >= elementData.length) { - throw new ConcurrentModificationException(); - } - while (i != size && modCount == expectedModCount) { - consumer.accept((E) elementData[offset + (i++)]); - } - // update once at end of iteration to reduce heap write traffic - lastRet = cursor = i; - checkForComodification(); - } - - public int nextIndex() { - return cursor; - } - - public int previousIndex() { - return cursor - 1; - } - - public void remove() { - if (lastRet < 0) - throw new IllegalStateException(); - checkForComodification(); - - try { - SubList.this.remove(lastRet); - cursor = lastRet; - lastRet = -1; - expectedModCount = ArrayList.this.modCount; - } catch (IndexOutOfBoundsException ex) { - throw new ConcurrentModificationException(); - } - } - - public void set(E e) { - if (lastRet < 0) - throw new IllegalStateException(); - checkForComodification(); - - try { - ArrayList.this.set(offset + lastRet, e); - } catch (IndexOutOfBoundsException ex) { - throw new ConcurrentModificationException(); - } - } - - public void add(E e) { - checkForComodification(); - - try { - int i = cursor; - SubList.this.add(i, e); - cursor = i + 1; - lastRet = -1; - expectedModCount = ArrayList.this.modCount; - } catch (IndexOutOfBoundsException ex) { - throw new ConcurrentModificationException(); - } - } - - final void checkForComodification() { - if (expectedModCount != ArrayList.this.modCount) - throw new ConcurrentModificationException(); - } - }; - } - - public List subList(int fromIndex, int toIndex) { - subListRangeCheck(fromIndex, toIndex, size); - return new SubList(this, offset, fromIndex, toIndex); - } - - private void rangeCheck(int index) { - if (index < 0 || index >= this.size) - throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); - } - - private void rangeCheckForAdd(int index) { - if (index < 0 || index > this.size) - throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); - } - - private String outOfBoundsMsg(int index) { - return "Index: "+index+", Size: "+this.size; - } - - private void checkForComodification() { - if (ArrayList.this.modCount != this.modCount) - throw new ConcurrentModificationException(); - } - - public Spliterator spliterator() { - checkForComodification(); - return new ArrayListSpliterator<>(ArrayList.this, offset, - offset + this.size, this.modCount); - } + return new ArraySubList<>(this, fromIndex, toIndex); } @Override @@ -1470,3 +1229,250 @@ modCount++; } } + +class ArraySubList extends AbstractList implements RandomAccess { + final ArrayList root; + final ArraySubList parent; + final int offset; + int size; + + /** + * Constructs a sublist of an arbitrary ArrayList. + */ + public ArraySubList(ArrayList root, int fromIndex, int toIndex) { + this.root = root; + this.parent = null; + this.offset = fromIndex; + this.size = toIndex - fromIndex; + this.modCount = root.modCount; + } + + /** + * Constructs a sublist of another ArraySubList. + */ + private ArraySubList(ArraySubList parent, int fromIndex, int toIndex) { + this.root = parent.root; + this.parent = parent; + this.offset = parent.offset + fromIndex; + this.size = toIndex - fromIndex; + this.modCount = root.modCount; + } + + public E set(int index, E element) { + rangeCheck(index); + checkForComodification(); + E oldValue = root.elementData(offset + index); + root.elementData[offset + index] = element; + return oldValue; + } + + public E get(int index) { + rangeCheck(index); + checkForComodification(); + return root.elementData(offset + index); + } + + public int size() { + checkForComodification(); + return size; + } + + public void add(int index, E element) { + rangeCheckForAdd(index); + checkForComodification(); + root.add(offset + index, element); + updateSizeAndModCount(1); + } + + public E remove(int index) { + rangeCheck(index); + checkForComodification(); + E result = root.remove(offset + index); + updateSizeAndModCount(-1); + return result; + } + + protected void removeRange(int fromIndex, int toIndex) { + checkForComodification(); + root.removeRange(offset + fromIndex, offset + toIndex); + updateSizeAndModCount(fromIndex - toIndex); + } + + public boolean addAll(Collection c) { + return addAll(this.size, c); + } + + public boolean addAll(int index, Collection c) { + rangeCheckForAdd(index); + int cSize = c.size(); + if (cSize == 0) + return false; + checkForComodification(); + root.addAll(offset + index, c); + updateSizeAndModCount(cSize); + return true; + } + + public Iterator iterator() { + return listIterator(); + } + + public ListIterator listIterator(int index) { + checkForComodification(); + rangeCheckForAdd(index); + + return new ListIterator() { + int cursor = index; + int lastRet = -1; + int expectedModCount = root.modCount; + + public boolean hasNext() { + return cursor != ArraySubList.this.size; + } + + @SuppressWarnings("unchecked") + public E next() { + checkForComodification(); + int i = cursor; + if (i >= ArraySubList.this.size) + throw new NoSuchElementException(); + Object[] elementData = root.elementData; + if (offset + i >= elementData.length) + throw new ConcurrentModificationException(); + cursor = i + 1; + return (E) elementData[offset + (lastRet = i)]; + } + + public boolean hasPrevious() { + return cursor != 0; + } + + @SuppressWarnings("unchecked") + public E previous() { + checkForComodification(); + int i = cursor - 1; + if (i < 0) + throw new NoSuchElementException(); + Object[] elementData = root.elementData; + if (offset + i >= elementData.length) + throw new ConcurrentModificationException(); + cursor = i; + return (E) elementData[offset + (lastRet = i)]; + } + + @SuppressWarnings("unchecked") + public void forEachRemaining(Consumer consumer) { + Objects.requireNonNull(consumer); + final int size = ArraySubList.this.size; + int i = cursor; + if (i >= size) { + return; + } + final Object[] elementData = root.elementData; + if (offset + i >= elementData.length) { + throw new ConcurrentModificationException(); + } + while (i != size && modCount == expectedModCount) { + consumer.accept((E) elementData[offset + (i++)]); + } + // update once at end of iteration to reduce heap write traffic + lastRet = cursor = i; + checkForComodification(); + } + + public int nextIndex() { + return cursor; + } + + public int previousIndex() { + return cursor - 1; + } + + public void remove() { + if (lastRet < 0) + throw new IllegalStateException(); + checkForComodification(); + + try { + ArraySubList.this.remove(lastRet); + cursor = lastRet; + lastRet = -1; + expectedModCount = root.modCount; + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + + public void set(E e) { + if (lastRet < 0) + throw new IllegalStateException(); + checkForComodification(); + + try { + root.set(offset + lastRet, e); + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + + public void add(E e) { + checkForComodification(); + + try { + int i = cursor; + ArraySubList.this.add(i, e); + cursor = i + 1; + lastRet = -1; + expectedModCount = root.modCount; + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + + final void checkForComodification() { + if (root.modCount != expectedModCount) + throw new ConcurrentModificationException(); + } + }; + } + + public List subList(int fromIndex, int toIndex) { + subListRangeCheck(fromIndex, toIndex, size); + return new ArraySubList<>(this, fromIndex, toIndex); + } + + private void rangeCheck(int index) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + + private void rangeCheckForAdd(int index) { + if (index < 0 || index > size) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + + private String outOfBoundsMsg(int index) { + return "Index: " + index + ", Size: " + size; + } + + private void checkForComodification() { + if (root.modCount != modCount) + throw new ConcurrentModificationException(); + } + + private void updateSizeAndModCount(int sizeChange) { + ArraySubList slist = this; + do { + slist.size += sizeChange; + slist.modCount = root.modCount; + slist = slist.parent; + } while (slist != null); + } + + public Spliterator spliterator() { + checkForComodification(); + return new ArrayList.ArrayListSpliterator<>(root, offset, + offset + size, modCount); + } +} + diff --git a/test/java/util/List/NestedSubList.java b/test/java/util/List/NestedSubList.java new file mode 100644 --- /dev/null +++ b/test/java/util/List/NestedSubList.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8079136 + * @run testng NestedSubList + * @summary Accessing a nested sublist leads to StackOverflowError + */ + +import java.util.AbstractList; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Vector; + +import org.testng.annotations.Test; +import org.testng.annotations.DataProvider; +import static org.testng.Assert.fail; + +public class NestedSubList { + + static final int NEST_LIMIT = 65536; + + @Test(dataProvider="sublists") + public void testAccessToSublists(List list, boolean modifiable) { + Class cls = list.getClass(); + for (int i = 0; i < NEST_LIMIT; ++i) { + list = list.subList(0, 1); + } + + try { + list.get(0); + if (modifiable) { + list.remove(0); + list.add(0, 42); + } + } catch (StackOverflowError e) { + fail("failed for " + cls); + } + } + + @DataProvider + public static Object[][] sublists() { + List c = Arrays.asList(42); + + return new Object[][] { + {c, false}, + {new ArrayList<>(c), true}, + {new LinkedList<>(c), true}, + {new MyList(), false}, + {new Vector<>(c), true}, + {Collections.singletonList(42), false}, + {Collections.checkedList(c, Integer.class), false}, + {Collections.checkedList(new ArrayList<>(c), Integer.class), true}, + {Collections.checkedList(new LinkedList<>(c), Integer.class), true}, + {Collections.checkedList(new Vector<>(c), Integer.class), true}, + {Collections.synchronizedList(c), false}, + {Collections.synchronizedList(new ArrayList<>(c)), true}, + {Collections.synchronizedList(new LinkedList<>(c)), true}, + {Collections.synchronizedList(new Vector<>(c)), true}, + {Collections.unmodifiableList(c), false}, + {Collections.unmodifiableList(new ArrayList<>(c)), false}, + {Collections.unmodifiableList(new LinkedList<>(c)), false}, + {Collections.unmodifiableList(new Vector<>(c)), false}, + }; + } + + static class MyList extends AbstractList { + public Integer get(int index) { return 42; } + public int size() { return 1; } + } +}