JDK-4819108 : (reflect) Method.invoke throws IllegalAccessException on inner class public meth
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 1.4.1
  • Priority: P3
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2003-02-14
  • Updated: 2012-09-28
  • Resolved: 2003-02-14
Related Reports
Duplicate :  
Description

Name: nt126004			Date: 02/14/2003


FULL PRODUCT VERSION :
java version "1.4.1_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_01-b01)
Java HotSpot(TM) Client VM (build 1.4.1_01-b01, mixed mode)


FULL OPERATING SYSTEM VERSION :
Microsoft Windows XP [Version 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
1) Using an object of an inner class, which defines public
methods cannot be invoked via "Method.invoke(...)", *if*
used from a class not belonging to the package.

2) The public methods of the very same object *can* be used
from a class not belonging to the package, if not using
reflection.

3) Studying the language specification, either the compiler
*and* reflection allow usage of public members of inner
classes (e.g. if the object was returned by getter method),
or *both* do not allow for it, irrespectible of whether the
using class belongs to the same package or not.

  To make this more understandable: if one receives an object
with e.g. the Enumeration interface implemented for it, then
one needs to be able to use those public methods. This is
the case, if one uses e.g. Hashtable (or the subclass
Properties) and wishes to enumerate the keys. Now, this
works for compiled Java programs, but using reflection
instead, does *not* work, but throws
"IllegalAccessException" instead. Obviously, if the compiler
does not cause that exception invoking the same public
methods via Method.invoke(...) must not throw that exception
either.

Hence the classification of a bug for this inconsistent
behaviour.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. compile the three enclosed (short) programs, note that
   two of them (RgfCase.java and RgfUseCase1.java) belong
   to the same package "rgfTest" (so they should reside
   in such a subdirectory)

2. the third program "RgfUseCase2.java" is *exactly*
   the same program as "RgfUseCase1.java", but with no
   package statement (hence this class does not belong
   to the above package)

3. make sure that the compiled Java programs of "rgfTest"
   package are retrievable and enter:

     java RgfUseCase1

   You see that using both, the compiled and the reflection
   version of accessing the public methods of the received
   object of the inner class in "RgfCase.java" work.

4. make sure that the compiled Java programs of "rgfTest"
   package are retrievable and enter:

     java RgfUseCase2

   You see, that *only* the compiled version works without
   a problem, but not the reflection version of accessing
   the public methods of the received object of the inner
   class in "RgfCase.java" work.

Either both in 4.) should work or none!

(Again, I did not find a place in the Java specification
where the contrary would be stated explicitly or implicitly.)

EXPECTED VERSUS ACTUAL BEHAVIOR :
- Expected results: if using reflection to invoke methods at
runtime, by default all methods should be invocable that can
be invoked with a compiled version.

Hence, it should be possible to invoke public methods on
received (and hence accessible!) objects via reflection, for
which implementors arbitrarily used inner classes (e.g. to
distinguish the implementation of interfaces from the outer
class as is the case e.g. Hashtable and Hashtable@Enumerator).

- Actual Results: whenever one invokes a *PUBLIC* method on
a received object, which is an instance of an inner class,
erroneously the "IllegalAccessException" is thrown!



ERROR MESSAGES/STACK TRACES THAT OCCUR :
As you can see,

1) the correct method is found (see signature),
2) the exception is raised by Method.invoke(), which is a native method.

-------------------- cut here -----------------------
       9/14: [hasMoreElements]

bean=[java.util.Hashtable$Enumerator@c23c12], args=[[Ljava.lang.Object;@c23de8],
m=[public boolean java.util.Hashtable$Enumerator.hasMoreElements()]
EngineUtils: in last catch:
       e=java.lang.IllegalAccessException: java/util/Hashtable$Enumerator
EngineUtils: trace stack:
- - - - - - - - - - - -
java.lang.IllegalAccessException: java/util/Hashtable$Enumerator
       at java.lang.reflect.Method.invoke(Native Method)
       at com.ibm.bsf.util.EngineUtils.callBeanMethod(EngineUtils.java:160)
       at com.ibm.bsf.engines.rexx.RexxAndJava.javaCallBSF(RexxAndJava.java:1339)
- - - - - - - - - - - -
com.ibm.bsf.BSFException: method invocation failed: java.lang.IllegalAccessExcep
tion: java/util/Hashtable$Enumerator
       at com.ibm.bsf.util.EngineUtils.callBeanMethod(EngineUtils.java:170)
       at com.ibm.bsf.engines.rexx.RexxAndJava.javaCallBSF(RexxAndJava.java:1339)
"invoke": got exception [method invocation failed:
java.lang.IllegalAccessException: java/util/Hashtable$Enumerator].
Exception in thread "main" com.ibm.bsf.BSFException: "invoke": object
'java.util.Hashtable$Enumerator@c23c12' - method [hasMoreElements], method not
found!
       at com.ibm.bsf.engines.rexx.RexxAndJava.javaCallBSF(RexxAndJava.java:1349)
  263 *-* call BSF "invoke", "java.util.Hashtable$Enumerator@c23c12",
"HASMOREELEMENTS"
  263 *-* interpret code        -- execute this dynamically created Rexx string

    9 *-*   do while enum~hasMoreElements       -- loop over enumeration
Error 40 running e:\rony\dev\bsf\bsf-2_2\lib\com\ibm\bsf\engines\rexx\BSF.cls
line 263:  Incorrect call to routine
Error 40.1:  External routine "BSF" failed
-------------------- cut here -----------------------

The documentation of Method.invoke() states that: "If this Method object
enforces Java language access control and the underlying method is inaccessible,
the invocation throws an IllegalAccessException."

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
----------------- cut here -----------------
// ###@###.###, Wirtschaftsuniversitaet Wien/Universitaet
Augsburg, 2003-02-13

package rgfTest;

public class RgfCase
{
    String [] info=null;
    int myCount=2;

    public RgfCase()
    {
        info=new String[myCount];
        info[0]="Hi, there.";
        info[1]="How do we do, today?";
    }

    public String toString ()
    {
        String t="";
        for (int i=0; i<myCount; i++) { t=t+"["+info[i]+"] "; }
        return t;
    }

    public java.util.Enumeration getEnumeration() { return new Enumerator(this); }

    class Enumerator implements java.util.Enumeration {
        int    count=0;
        RgfCase obj=null;
        Enumerator (RgfCase r) { obj=r; }

        public boolean hasMoreElements() { return count < obj.myCount; }

        public Object nextElement() { return obj.info[count++]; }
    }
}
----------------- cut here -----------------


----------------- cut here -----------------
// ###@###.###, Wirtschaftsuniversitaet Wien/Universitaet
Augsburg, 2003-02-13
package rgfTest;

public class RgfUseCase1        // same package as "RgfTest"
{
    public static void main (String args[]) {
        rgfTest.RgfCase r=new rgfTest.RgfCase();    // create an instance
        System.out.println("r.toString=["+r+"]");
        java.util.Enumeration e=r.getEnumeration();
        System.out.println("\nNow using an enumeration:");
        while (e.hasMoreElements()) {
            System.out.println("\t"+e.nextElement());
        }

        System.out.println("\nNow using reflection to access a public method of
the inner class:");
        Class ec=e.getClass();
        java.lang.reflect.Method [] em=ec.getMethods();   // get the inner class
methods

        int i;
        Object o;
        for (i=0; i<em.length; i++) {
            if ("hasMoreElements".equalsIgnoreCase(em[i].getName())) break;
        }

        if (i<em.length) {
            System.out.println("\tfound: ["+em[i]+"].\n\tnow invoking the method:");
            try {
                o=em[i].invoke(e, null);
                System.out.println("\tresult from invoking the method: ["+o+"]
(should be 'false')");
            }
            catch (java.lang.Exception exc)
            {
                System.err.println("caught exception: "+exc);
                exc.printStackTrace();
                return;
            }
        }

    }
}
----------------- cut here -----------------

----------------- cut here -----------------
// ###@###.###, Wirtschaftsuniversitaet Wien/Universitaet
Augsburg, 2003-02-13
/* NOT part of the package ('package rgfTest;') anymore! */


public class RgfUseCase2        // same package as "RgfTest"
{
    public static void main (String args[]) {
        rgfTest.RgfCase r=new rgfTest.RgfCase();    // create an instance
        System.out.println("r.toString=["+r+"]");
        java.util.Enumeration e=r.getEnumeration();
        System.out.println("\nNow using an enumeration:");
        while (e.hasMoreElements()) {
            System.out.println("\t"+e.nextElement());
        }

        System.out.println("\nNow using reflection to access a public method of
the inner class:");
        Class ec=e.getClass();
        java.lang.reflect.Method [] em=ec.getMethods();   // get the inner class
methods

        int i;
        Object o;
        for (i=0; i<em.length; i++) {
            if ("hasMoreElements".equalsIgnoreCase(em[i].getName())) break;
        }

        if (i<em.length) {
            System.out.println("\tfound: ["+em[i]+"].\n\tnow invoking the method:");
            try {
                o=em[i].invoke(e, null);
                System.out.println("\tresult from invoking the method: ["+o+"]
(should be 'false')");
            }
            catch (java.lang.Exception exc)
            {
                System.err.println("caught exception: "+exc);
                exc.printStackTrace();
                return;
            }
        }

    }
}
----------------- cut here -----------------

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

CUSTOMER WORKAROUND :
A workaround for 1.2, 1.3, 1.4, but *not* for 1.1 is as follows:

- use AccessibleObject.setAccessible(true) on the methods
(members), you wish to access

side-effect: using this removes *any* access control (hence
I check whether the method has the 'public' modifier set,
before invoking setAccessible(true) on the method object).
(Review ID: 181276) 
======================================================================

Comments
EVALUATION Another example of reflection not understanding inner class scoping. -- iag@sfbay 2003-02-14
14-02-2003