Possible bug in sun.reflect.Reflection handling of abstract enums?
I've identified what is at least undesirable behavior and at most a bug in the Sun JDK's handling of reflection on Java enum
s with an abstract method. I've searched for a bug report and StackOverflow answer for this particular behavior and come up dry. You're more or less always wrong when you think you've found an issue like this in such well-used and carefully-tested code, so please sanity check me and tell me where I've gotten this wrong.
The Code
Consider the following code:
a/Greeting.java
package a;
public enum Greeting {
HELLO {
@Override
public void greet() {
System.out.println("Hello!");
}
};
public abstract void greet();
}
b/EnumTest.java
package b;
import java.lang.reflect.Method;
import a.Greeting;
public class EnumTest {
public static void main(String[] args) throws Exception {
Greeting g=Greeting.HELLO;
Method greet=g.getClass().getMethod("greet");
System.out.println("Greeting "+g.getClass()+" ...");
greet.invoke(g);
System.out.println("Greeted!");
}
}
Also, please note that Greeting
and EnumTest
are in different packages. (This ends up mattering.)
The Error
When you run this code, you expect to get the following output:
Greeting class a.Greeting ...
Hello!
Greeted!
Instead, you get the following output:
Greeting class a.Greeting$1 ...
Exception in thread "main" java.lang.IllegalAccessException: Class b.EnumTest can not access a member of class a.Greeting$1 with modifiers "public"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:95)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:261)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:253)
at java.lang.reflect.Method.invoke(Method.java:594)
at b.EnumTest.main(EnumTest.java:13)
Understanding the Behavior
First, please note that Greeting
is public
and Greeting$greet
is public
. (Even the error message indicates public
access!) So what's going on?
What The Heck is Going On Here?
If you step through the code, you find that the ultimate "problem" is that sun.reflect.Reflection$verifyMemberAccess()
returns false
. (So, the Reflection API claims we do not have access to this method.) The particular code that fails is here:
public static boolean verifyMemberAccess(Class currentClass,
// Declaring class of field
// or method
Class memberClass,
// May be NULL in case of statics
Object target,
int modifiers)
// ...
if (!Modifier.isPublic(getClassAccessFlags(memberClass))) {
isSameClassPackage = isSameClassPackage(currentClass, memberClass);
gotIsSameClassPackage = true;
if (!isSameClassPackage) {
return false;
}
}
// ...
Essentially, this method determines whether code in currentClass
can see members of memberClass
with modifiers of modifiers
.
Clearly, we should have access. We're calling a public
method in a public
class! However, this code returns false
, in the indicated return
statement. Therefore, the class of the value we're trying to invoke the method on is not public
. (We know this because the outer test -- !Modifier.isPublic(getClassAccessFlags(memberClass))
-- passes, since the code reaches the inner return
.) But Greeting
is public
!
However, the type of Greeting.HELLO
is not a.Greeting
. It's a.Greeting$1
! (As careful readers will have noticed above.)
enum
classes with one or more abstract
methods create child classes under the covers (one for each constant). So what's happening is that the "under the covers" child classes are not marked public
, so we're not allowed to see public
methods on those classes. Bummer.
Confirmation of the Theory
To test this theory, we can invoke the superclass enum
's greet()
method on the child instead:
public static void main(String[] args) throws Exception {
Greeting g=Greeting.HELLO;
Method greet=g.getClass().getSuperclass().getMethod("greet");
System.out.println("Greeting "+g.getClass()+" ...");
greet.invoke(g);
System.out.println("Greeted!");
}
...and meet with success:
Greeting class a.Greeting$1 ...
Hello!
Greeted!
Also, if we move a.Greeting
to b.Greeting
(the same package as b.EnumTest
), that works too, even without the getSuperclass()
call.
So... Bug or No?
So... is this a bug? Or is this simply undesired behavior that is an artifact of the underlying implementation? I checked the relevant section of the Java Language Specification and this syntax is legal. Also, the specification doesn't specify how child classes will be arranged, so while this technically in violation of the standard (or at least the part of the standard I read), I'm inclined to call this a bug.
What does StackOverflow think: is this a bug, or simply undesired behavior? I realize this is a bit of an unconventional question, so please forgive the format.
Also, I'm on a Mac (in case that matters), and java -version
prints the following, for anyone who wants to reproduce:
$ java -version
java version "1.7.0_21"
Java(TM) SE Runtime Environment (build 1.7.0_21-b12)
Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)
EDIT : Interesting to find a bug open for an similar (at least related) issue since 1997: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4071957
EDIT : Per the answer below, the JLS does say that enum
classes with an abstract method shall behave like anonymous classes:
The optional class body of an enum constant implicitly defines an anonymous class declaration (§15.9.5) that extends the immediately enclosing enum type. The class body is governed by the usual rules of anonymous classes
Per the bug above, anonymous class handling has been a "bug" since 1997. So with respect to whether this is actually a bug or not is a bit semantic at this point. Bottom line: don't do this, since it doesn't work and it's not likely to in the future. :)
Not a bug.
As a careful examination of the exception message shows, the problem class is a.Greeting$1
. That's an anonymous inner class. The method happens to be public and irrelevantly the enclosing class and the static field it is assigned to are public, but the actual class is non-public.
a.Greeting.class
and a.Greeting.HELLO.getClass().getSuperclass()
should work.
Therefore, the class of the value we're trying to invoke the method on is not public.
Class.getMethod
operates on a class (Class) not a value, so that's irrelevant (unless you were trying to get the Field
a.Greeting.HELLO
.
EDIT : From the Java Language Specification:
"The optional class body of an enum constant implicitly defines an anonymous class declaration (§15.9.5) that extends the immediately enclosing enum type. The class body is governed by the usual rules of anonymous classes; ..."
So the enum
is treated as an anonymous class, and this is how anonymous classes work.