JDK-8114832 : it.next on ArrayList throws wrong type of Exception after remove(-1)
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util:collections
  • Affected Version: 8u45,9
  • Priority: P5
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2015-06-12
  • Updated: 2018-09-11
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
/Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Darwin amgdh077.o.aist.go.jp 13.3.0 Darwin Kernel Version 13.3.0: Tue Jun  3 21:27:35 PDT 2014; root:xnu-2422.110.17~1/RELEASE_X86_64 x86_64 i386 MacPro6,1 Darwin


A DESCRIPTION OF THE PROBLEM :
(1) create new ArrayList
(2) get iterator on list
(3) call remove(-1) on list -> IndexOutOfBoundsException
(4) call iterator.next

Result: ConcurrentModification even though the list was never modified (it was always empty). Should be: NoSuchElementException.

Result is correct if remove(0) is called, or if LinkedList is used.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
See source code`

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
NoSuchElementException
ACTUAL -
ConcurrentModificationException

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class iterator_test {
  public static void main(String[] argv) {
    /* BUG: Sequence of arrayList.iterator, arrayList.remove(-1), iterator.next
       produces wrong type of Exception. */
    ArrayList<Integer> testArrayList = new ArrayList<Integer>();
    Iterator<Integer> it = testArrayList.iterator();
    try {
      testArrayList.remove(-1);
    } catch (IndexOutOfBoundsException e) {
    }
    try {
      it.next();
    } catch (ConcurrentModificationException e) {
      System.err.println("Should be NoSuchElementException!");
    }

    /* Correct result with remove(0) */
    testArrayList = new ArrayList<Integer>();
    it = testArrayList.iterator();
    try {
      testArrayList.remove(0);
    } catch (IndexOutOfBoundsException e) {
    }
    try {
      it.next();
    } catch (NoSuchElementException e) {
    }

    /* Correct result with remove(-1) on LinkedList */
    LinkedList<Integer> testLinkedList = new LinkedList<Integer>();
    it = testLinkedList.iterator();
    try {
      testLinkedList.remove(-1);
    } catch (IndexOutOfBoundsException e) {
    }
    try {
      it.next();
    } catch (NoSuchElementException e) {
    }
  }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Not available.


Comments
I modified Cyrille's latest test as follows: import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Vector; import java.util.concurrent.CopyOnWriteArrayList; public class JDK_8114832 { public static void main(String[] argv) { test(new ArrayList<>()); test(new Vector<>()); test(new LinkedList<>()); test(new CopyOnWriteArrayList<>()); } public static void test(List<Integer> list) { list.add(42); Iterator<Integer> it = list.iterator(); try { list.remove(-1); throw new AssertionError("shouldThrow"); } catch (IndexOutOfBoundsException expected) {} try { if (it.next() != 42) throw new AssertionError(); } catch (ConcurrentModificationException e) { System.err.printf("%s: Should be 42!%n", list.getClass().getSimpleName()); } } } giving ==> java JDK_8114832 ArrayList: Should be 42! Vector: Should be 42! I looked again at the various arguments, and I don't see how the latest test changes the evaluation. It's unfortunate that different JDK implementations made different choices, but the damage is done ... Cyrille's qualifications are impressive, and we share having worked at AIST Tsukuba, but my evaluation remains unchanged - it's best to DO NOTHING.
05-03-2016

On 11/08/2015 2:13 PM, Cyrille Artho wrote: > Dear all, > I'd like to write a response to my bug report at > https://bugs.openjdk.java.net/browse/JDK-8114832 > but I didn't get an OpenJDK login when I submitted the report. > > In this particular case, it should be noted that my test was minimal. If > one has at least one element in the list before the iterator is created, > then it.next() throws no exception when used on any collection except > ArrayList/Stack/Vector. > > This means that ArrayList and its legacy brethren are the only classes > that generate a spurious exception, and only after list.remove(-1) was > called before using the iterator. The spurious exception prevents future > access to data where other containers work as expected. > > Currently it is being considered to keep the current (faulty) behavior, > but based on the new test case, I strongly advise correcting this bug. > > With the new test case, the information about the bug becomes: > > EXPECTED - > it.next() returns 42 > ACTUAL - > ConcurrentModificationException > > REPRODUCIBILITY : > This bug can be reproduced always. > > ---------- BEGIN SOURCE ---------- > import java.util.ArrayList; > import java.util.LinkedList; > import java.util.ConcurrentModificationException; > import java.util.Iterator; > import java.util.NoSuchElementException; > > public class iterator_test { > public static void main(String[] argv) { > /* BUG: Sequence of arrayList.iterator, > arrayList.add(new Integer(1)), > arrayList.remove(-1), > iterator.next > produces ConcurrentModificationException. */ > ArrayList<Integer> testArrayList = new ArrayList<Integer>(); > testArrayList.add(new Integer(42)); > Iterator<Integer> it = testArrayList.iterator(); > try { > testArrayList.remove(-1); > } catch (IndexOutOfBoundsException e) { > } > try { > Integer result = it.next(); > assert(result == 42); > } catch (ConcurrentModificationException e) { > System.err.println("Should be 42!"); > } > } > } > > ---------- END SOURCE ----------
20-08-2015

I can't quite tell why Steve is looking to change this now but at least the existing behavior will catch mis-uses that might otherwise only surface when something changes that causes the remove to succeed. So I think I agree with the recent comments to not change it.
26-07-2015

"""if multiple precondition violations occur simultaneously, for which one should the exception be thrown?""" It's slightly more subtle - can a failed modification be the trigger for a later ConcurrentModificationException? I'd say yes - attempted murder is still a crime! Consistency is a virtue, so in the unlikely event that we do decide to fix this, we should fix it "everywhere", and write tests to ensure we do. And voila, you have a career path, not a simple bug fix!
25-07-2015

I agree with Martin's comments. This is an instance of a general issue, which is that if multiple precondition violations occur simultaneously, for which one should the exception be thrown? In some sense it would be more correct to specify the order in which all preconditions are checked, thus governing which exception occurs if there are multiple violations. In practice, however, we don't specify this, nor are we very consistent about it. I don't see the value in dealing with this either at the specification level or the coding level, unless there is some specific reason to prefer one exception over another.
24-07-2015

Having maintained many of these classes, I was previously aware that there is an inconsistency - some methods update modCount on successful modification, others before. It is not immediately obvious which is better - even concurrent __attempted__ modification is arguably a bug and so throwing ConcurrentModificationException is reasonable. Because the historic behavior is reasonable (although it would have been better to be super-consistent one way or the other) and because we should have a high bar for any behavior change (is it really a bug fix?) I think it is best to DO NOTHING - don't fix this non-bug.
24-07-2015

It's likely this behaviour was observable from Java 7 and perhaps earlier. The remove method does this: public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); ... } The rangeCheck method only checks if index>=size and expects it be used immediately prior to an array access. But in this case the mod count is incremented before the array access. The simple fix is to move the mod count increment to occur after the array access. The same behaviour also occurs for Vector.
17-06-2015

1. Run the attached test case iterator_test.java in MAC OS X and Linux (64-bit). 2. Checked this for JDK 8, 8u45, 8u60 ea b19, and 9 ea b68. 8: FAIL 8u45: FAIL 8u60 ea b19: FAIL 9 ea b68: FAIL 3. Output with JDK 8u45: ----------------------------------------- > java iterator_test Picked up _JAVA_OPTIONS: -Dcom.oracle.usagetracker.config.file="C:\ProgramData\Oracle\Java_AMC_2\jutConfig.txt" Should be NoSuchElementException! 4. Conclusion: This issue is reproducible with JDK 8u45 as reported by the submitter. Moving this up for further evaluation.
17-06-2015