JDK-4283544 : (reflect) Field and Method do not correctly obey language accessibility rules
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 1.1,1.2.2,5.0
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic,solaris_2.5,windows_xp
  • CPU: generic,x86,sparc
  • Submitted: 1999-10-21
  • Updated: 2017-12-07
Related Reports
Duplicate :  
Duplicate :  
Duplicate :  
Relates :  
Relates :  
Description
java.lang.reflect.Field (get* and set*) and Method (invoke) base their access check on the declaring class.  This is contrary to the JLS, which defines accessibility in terms of the reference type.  Consider the following:

package foo;

class X {
    public int i;
}

package foo;

public class Y extends X {
    public static Object get() { return new Z(); }
}

package foo;

class Z extends Y {}

package bar;

import foo.Y;

public class T {
    public static void main(String[] args) {
	Y y = new Y();
	int i = y.i;
	try {
	    y.getClass().getField("i").getInt(y);
	} catch (Exception e) {
	    e.printStackTrace();
	}
	Object z = Y.get();
	i = ((Y)z).i;
	try {
	    z.getClass().getField("i").getInt(z);
	} catch (Exception e) {
	    e.printStackTrace();
	}
    }
}

Both of the getInt calls throw IllegalAccessException, contrary to what the compiler allows.

At runtime you need to check if there exists some class, between the declaring class and the actual class (inclusive), that is public.  This could be a bit messy for interface methods, since there isn't a single linear chain to search.

Comments
There may be some room for pushing this case a little further towards the way it behaves in source code. For the static members at least, since they currently (after the fix for JDK-8172190) ignore the 'target' parameter passed to Field.[get|set] and Method.invoke. The spec. could be compatibly changed so that this parameter would be used for static members in the following way: If an instance of Class object is passed to the Field.[get|set] or Method.invoke 'target' parameter for a static field or method and this Class object represents a sub-type of the member's declaring type and the member's declaring type is either not an interface or the member is a static field (interface static methods are not inherited), the accessibility checks also consider passed-in Class access modifiers and package accessibility (whether it is exported to the caller) in access decision. The access is granted in case when the member is accessible and either the declaring class or the passed-in subclass are accessible. The presented test case will work with such changed spec. and everything that works now will still work (the access checks are only loosened). But I don't know whether this is actually a wise way to go. The presented test case is something that very rarely happens in real code I think. This could also be considered a "feature" not a "bug". With strong encapsulation provided by jigsaw, if you want to prevent static members to be reflectively accessed (for whatever reason) and still allow them to be accessed from code, you can "hide" them in a package-private class from which you inherit them by subclassing.
16-06-2017

Here's a simpler example copied from JDK-8181410, which was closed as a duplicate of this bug. ========== x/B.java ========== package x; class A { public static final String VALUE = "foo"; } public class B extends A { } ========== y/Test.java ========== package y; import java.lang.reflect.Field; import x.B; public class Test { public static void main(String[] args) throws Exception { System.out.println(B.VALUE); // works System.out.println(B.class.getField("VALUE").get(null)); // IllegalAccessException } } ========== While it might be "obvious" that this should work, things aren't always obvious. I had a discussion with Alex Buckley (JLS editor) about this the other day, and his statement was that a reflective Field object (and Method and Constructor) represent the **declaration** of that element, as opposed to the **use** of that element. The JLS rules for accessibility cover how elements are used, whereas reflective objects are about declarations. One might consider that reflection should be modified or enhanced somehow to cover uses, but as things stand today, it doesn't. Some examples will help make this clearer. Continuing with the above example, suppose we have a reference to class x.A. Clearly, the source code of Test.java cannot refer x.A.class since A is not public. However, there are many ways that a program can get its hands on a class object for a non-public class, e.g., via an instance, via B.class.getSuperclass(), or via Class.forName: Class<?> classA = Class.forName("x.A"); Now the program can get a field from classA: Field fieldFromA = classA.getField("VALUE"); However, having a valid Field object doesn't mean that it can be used: System.out.println(fieldFromA.get(null)); // throws IllegalAccessException But what if we were to get the field via B? Field fieldFromB = B.class.getField("VALUE"); System.out.println(fieldFromB.get(null)); // throws IllegalAccessException But it turns out that fieldFromA.equals(fieldFromB) is true. The Field object representing x.A.VALUE represents the declaration of x.A.VALUE, no matter what path or mechanism was used to obtain that Field object. The get() method checks whether the field is directly accessible to the calling code, and since it isn't, it throws IllegalAccessException. The same applies to Method and Constructor objects. The complaint is that reflection doesn't obey the language accessibility rules. This is true, and unfortunately, this isn't well documented. I doubt that reflection can be modified or retrofitted to follow the language (at least, while preserving compatibility), so perhaps the best thing for this is simply to close this as Won't Fix.
15-06-2017

EVALUATION $ 141; java bar.T java.lang.IllegalAccessException: Class bar.T can not access a member of class foo.X with modifiers "public" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:57) at java.lang.reflect.Field.doSecurityCheck(Field.java:811) at java.lang.reflect.Field.getFieldAccessor(Field.java:758) at java.lang.reflect.Field.getInt(Field.java:369) at bar.T.main(T.java:10) java.lang.IllegalAccessException: Class bar.T can not access a member of class foo.X with modifiers "public" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:57) at java.lang.reflect.Field.doSecurityCheck(Field.java:811) at java.lang.reflect.Field.getFieldAccessor(Field.java:758) at java.lang.reflect.Field.getInt(Field.java:369) at bar.T.main(T.java:17) This is the method/field version of 4071957. This problem is best solved via the updated classfile format which is being considered for Tiger. -- iag@sfbay 2002-05-16
16-05-2002