JDK-6923689 : Enhanced for loop with unsafe generics is incorrectly desugared.
  • Type: Bug
  • Component: specification
  • Sub-Component: language
  • Affected Version: 6u18
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2010-02-05
  • Updated: 2014-02-26
  • Resolved: 2011-07-21
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.
JDK 7
7 rcFixed
Related Reports
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
$ java -version
java version "1.6.0_0"
OpenJDK Runtime Environment (IcedTea6 1.6) (fedora-33.b16.fc12-x86_64)
OpenJDK 64-Bit Server VM (build 14.0-b16, mixed mode)

Also latest jdk 1.6 on windows, and jdk 1.7 prerelease.

ADDITIONAL OS VERSION INFORMATION :
(irrelevant).

A DESCRIPTION OF THE PROBLEM :
Attached code contains two loops; one an enhanced for-loop, the other the for-loop manually desugared as per JLS3 14.14.2.

They should behave identically. They do not. The manually desugared loop generates a class-cast exception, while the enhanced for-loop does not.

Inspecting the byte-code output from the compiler, the result of .iterator() is not-being run-time type-checked before being stored to the synthesized variable. Such a check is necessary when the type after erasure differs from the unerased type: the desugar from the JLS is:

for (I #i = Expression.iterator(); #i.hasNext(); ) {

        VariableModifiersopt Type Identifier = #i.next();
   Statement
}

In the example below, Expression.iterator() has type MyIterator, but after erasure the type is Iterator, and thus a run-time type check is necessary.

Plausibly, this could be resolved by changing the JLS, rather than javac, by specifying the type of #i to be Iterator<Type>, where Type is as per 14.14.2.


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------

import java.util.ArrayList;
import java.util.Iterator;

class MyIterable<T extends Iterator<String> > implements Iterable<String> {
    public T iterator() { return stored; }
    public T stored;
}

class MyIterator implements Iterator<String> {

    public boolean hasNext() { return stored.hasNext(); }
    public String next() { return stored.next(); }
    public void remove() { }

    public Iterator<String> stored;
}

public class Temp
{
    public static void main(String[] args) {

        MyIterable<MyIterator> u = new MyIterable<MyIterator>();

        MyIterable unsafe = u;
        unsafe.stored = new ArrayList<String>().iterator();

        System.out.println ("Sugared....");
        for (String s : u)
            System.out.println (s);

        System.out.println ("Unsugared....");
        for (MyIterator i = u.iterator(); i.hasNext(); ) {
            String s = i.next();
            System.out.println (s);
        }
    }
}

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

Comments
EVALUATION Both javac and Eclipse behave differently than specified by JLS 3. Since there is no fundamental problem with the compilers' behavior, it is best to treat this as a specification problem. The text should be rewritten to define the desugared type, I, in terms of the parameterization of Iterable that is a supertype of the expression.
04-04-2011

PUBLIC COMMENTS Two other examples. All these problems are exposed by a covariant return-type override in a subclass, which seems to not have been anticipated by the specification. This illustrates that the compiler uses the Iterator type arguments for checking, not the type of expr.iterator().next(): static class MyIterable implements Iterable<Number> { public MyIterator iterator() { return stored; } public MyIterator stored; } static class MyIterator implements Iterator<Number> { public boolean hasNext() { return stored.hasNext(); } public Integer next() { return stored.next(); } public void remove() { } public Iterator<Integer> stored; } public static void main(String[] args) { MyIterable u = new MyIterable(); System.out.println ("Sugared...."); for (Integer s : u) // compiler error System.out.println (s); System.out.println ("Unsugared...."); for (MyIterator i = u.iterator(); i.hasNext(); ) { Integer s = i.next(); System.out.println (s); } } -------------- This illustrates that the compiler uses the Iterable type arguments for checking, not the type of expr.iterator(): static class MyIterable implements Iterable { public MyIterator iterator() { return stored; } public MyIterator stored; } static class MyIterator implements Iterator<Integer> { public boolean hasNext() { return stored.hasNext(); } public Integer next() { return stored.next(); } public void remove() { } public Iterator<Integer> stored; } public static void main(String[] args) { MyIterable u = new MyIterable(); System.out.println ("Sugared...."); for (Integer s : u) // compiler error System.out.println (s); System.out.println ("Unsugared...."); for (MyIterator i = u.iterator(); i.hasNext(); ) { Integer s = i.next(); System.out.println (s); } }
04-04-2011

EVALUATION Compiler generates potentially unsafe code.
31-08-2010