JDK-8167262 : java.lang.IllegalAccessException When Reflecting on Iterator.hasNext()
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 8
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: generic
  • CPU: generic
  • Submitted: 2016-09-30
  • Updated: 2016-10-07
  • Resolved: 2016-10-07
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]

A DESCRIPTION OF THE PROBLEM :
Trying to reflect on the iterator.hasNext(), a java.lang.IllegalAccessException is thrown the Inner Class. See Related Review ID: JI-9044075 for a similar example.

REGRESSION.  Last worked in version 8u101

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Save the Source Code in a file called: InnerClassIssue.java
Compile it: javac InnerClassIssue.java
Run it: java InnerClassIssue


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The program should print the letters a-e on separate lines several times. Each time a different way of printing the letters is used. In total, the letters a-e should be printed 6 times prefixed by "way x" where x is 1, 2 or 3, referencing the print and access method used.
ACTUAL -
The program prints the letters a - e on separate lines using different ways. The List and Set interfaces are used as examples.

The List is created. a-e is loaded into the list and then the list is printed using the list.iterator() hasNext() and next() methods directly (works as expected). Then the iterator is gotten via introspection and printed using the list.iterator() hasNext() and next() methods directly (works as expected). Then the iterator is gotten and printed using reflection on the iterator's hasNext() and next() methods (fails with exception). I would expect this last attempt to behave just like the first two.

The Set is used as a second example and results in similar behavior.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
way 1:
a
b
c
d
e
way 2:
a
b
c
d
e
java.lang.IllegalAccessException: Class InnerClassIssue can not access a member of class java.util.ArrayList$Itr with modifiers "public"
        at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
        at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
        at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
        at java.lang.reflect.Method.invoke(Method.java:491)
        at InnerClassIssue.toStringI11n(InnerClassIssue.java:123)
        at InnerClassIssue.toString3(InnerClassIssue.java:72)
        at InnerClassIssue.main(InnerClassIssue.java:30)
way 1:
a
b
c
d
e
way 2:
a
b
c
d
e
java.lang.IllegalAccessException: Class InnerClassIssue can not access a member of class java.util.TreeMap$PrivateEntryIterator with modifiers "public final"
        at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
        at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
        at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
        at java.lang.reflect.Method.invoke(Method.java:491)
        at InnerClassIssue.toStringI11n(InnerClassIssue.java:123)
        at InnerClassIssue.toString3(InnerClassIssue.java:95)
        at InnerClassIssue.main(InnerClassIssue.java:44)


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
public class InnerClassIssue {

    public static void main(String[] args) throws Exception {
        try {
            java.util.List<String> list = new java.util.ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.add("e");
            System.err.println("way 1:\n"+toString1(list));
            System.err.println("way 2:\n"+toString2(list));
            System.err.println("way 3:\n"+toString3(list));
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            java.util.Set<String> set = new java.util.TreeSet<>();
            set.add("a");
            set.add("b");
            set.add("c");
            set.add("d");
            set.add("e");
            System.err.println("way 1:\n"+toString1(set));
            System.err.println("way 2:\n"+toString2(set));
            System.err.println("way 3:\n"+toString3(set));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Introspection: I11n

    //
    // toString: list
    //

    // toString1: NoI11n used
    private static String toString1(java.util.List<String> list) {
        return toStringNoI11n(list.iterator());
    }

    // toString2: Use I11n on set.iterator()
    private static String toString2(java.util.List<String> list)
    throws Exception {
        java.lang.reflect.Method m = list.getClass()
            .getMethod("iterator");
        return toStringNoI11n((java.util.Iterator)m.invoke(list));
    }

    // toString3: Use I11n on iterator.hasNext() and iterator.next()
    private static String toString3(java.util.List<String> list)
    throws Exception {
        return toStringI11n(list.iterator());
    }

    //
    // toString: set
    //

    // toString1: NoI11n used
    private static String toString1(java.util.Set<String> set) {
        return toStringNoI11n(set.iterator());
    }

    // toString2: Use I11n on set.iterator()
    private static String toString2(java.util.Set<String> set)
    throws Exception {
        java.lang.reflect.Method m = set.getClass()
            .getMethod("iterator");
        return toStringNoI11n((java.util.Iterator)m.invoke(set));
    }

    // toString3: Use I11n on iterator.hasNext() and iterator.next()
    private static String toString3(java.util.Set<String> set)
    throws Exception {
        return toStringI11n(set.iterator());
    }

    //
    // toString: itarator
    //

    // toStringNoI11n: NoI11n used
    private static String toStringNoI11n(java.util.Iterator<String> i) {
        StringBuilder sb = new StringBuilder();
        if (i.hasNext()) {
            sb.append(i.next());
            while (i.hasNext()) {
                sb.append("\n");
                sb.append(i.next());
            }
        }
        return sb.toString();
    }

    // toStringI11n: Use I11n on iterator.hasNext() and iterator.next()
    private static String toStringI11n(java.util.Iterator<String> i)
    throws Exception {
        StringBuilder sb = new StringBuilder();
        java.lang.reflect.Method mhn = i.getClass().getMethod("hasNext");
        java.lang.reflect.Method mn = i.getClass().getMethod("next");
        // fails on first invoke
        // java.lang.IllegalAccessException: Class InnerClassIssue can not access a member of class java.util.TreeMap$PrivateEntryIterator with modifiers "public final"
        if ((boolean)mhn.invoke(i)) {
            sb.append(mn.invoke(i));
            while ((boolean)mhn.invoke(i)) {
                sb.append("\n");
                sb.append(mn.invoke(i));
            }
        }
        return sb.toString();
    }

}

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

CUSTOMER SUBMITTED WORKAROUND :
The toArray() method can be used in both the List and Set case but this defeats the purpose of having an iterator in the first place. Without seeing the code, it seems that Inner classes need to be removed from any class their contained in. Since Java is suppose to be object oriented and reusable, hiding a class within a class inherently defeats this feature. As can be seen in this example, an Iterator is a fundamental construct yet can not be reflected upon which it clearly should be. If inner classes aren't  used, the code should collapse since the inner class will now be reusable (and this bug should go away). Just because you can do something doesn't mean you should do it. That something in this case is embed a class in a class. It introduces more problems than it solves, this bug for example. By allowing the end user to cut corners and create inner classes, they too are introducing this same bug and nonreusability. In short, this feature needs to be redesigned and inner classes removed.


Comments
This is not a bug, it is instead just that obj.getClass().getMethod(...) is an anti-pattern because obj.getClass() may return a type that is not accessible and getMethod may return a public Method on that type. For the example then changing i.getClass() to Iterator.class will fix the issue as these will return the Iterator next and hasNext rather than the methods on the inaccessible implementation class.
06-10-2016

To reproduce the issue, run the attached test case. Following are the results on the various JDK versions: JDK 8u102 - Fail JDK 8u122ea - Fail JDK 9ea + 137 - Fail Following is the output on various JDK versions: --------------------------------------------------------------------------------------- way 1: a b c d e way 2: a b c d e java.lang.IllegalAccessException: class JI9044176 cannot access a member of class java.util.ArrayList$Itr (in module java.base) with modifiers "public" at jdk.internal.reflect.Reflection.throwIllegalAccessException(java.base@9-ea/Reflection.java:405) at jdk.internal.reflect.Reflection.throwIllegalAccessException(java.base@9-ea/Reflection.java:396) at jdk.internal.reflect.Reflection.ensureMemberAccess(java.base@9-ea/Reflection.java:98) at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(java.base@9-ea/AccessibleObject.java:355) at java.lang.reflect.AccessibleObject.checkAccess(java.base@9-ea/AccessibleObject.java:347) at java.lang.reflect.Method.invoke(java.base@9-ea/Method.java:529) at JI9044176.toStringI11n(JI9044176.java:107) at JI9044176.toString3(JI9044176.java:56) at JI9044176.main(JI9044176.java:14) way 1: a b c d e way 2: a b c d e java.lang.IllegalAccessException: class JI9044176 cannot access a member of class java.util.TreeMap$PrivateEntryIterator (in module java.base) with modifiers "public final" at jdk.internal.reflect.Reflection.throwIllegalAccessException(java.base@9-ea/Reflection.java:405) at jdk.internal.reflect.Reflection.throwIllegalAccessException(java.base@9-ea/Reflection.java:396) at jdk.internal.reflect.Reflection.ensureMemberAccess(java.base@9-ea/Reflection.java:98) at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(java.base@9-ea/AccessibleObject.java:355) at java.lang.reflect.AccessibleObject.checkAccess(java.base@9-ea/AccessibleObject.java:347) at java.lang.reflect.Method.invoke(java.base@9-ea/Method.java:529) at JI9044176.toStringI11n(JI9044176.java:107) at JI9044176.toString3(JI9044176.java:79) at JI9044176.main(JI9044176.java:28) -------------------------------------------------------------------------------------------------
06-10-2016