Note that there are some explanatory texts on larger screens.

plurals
  1. POPossible bug in sun.reflect.Reflection handling of abstract enums?
    primarykey
    data
    text
    <p>I've identified what is at least undesirable behavior and at most a bug in the Sun JDK's handling of reflection on Java <code>enum</code>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.</p> <h1>The Code</h1> <p>Consider the following code:</p> <h3>a/Greeting.java</h3> <pre><code>package a; public enum Greeting { HELLO { @Override public void greet() { System.out.println("Hello!"); } }; public abstract void greet(); } </code></pre> <h3>b/EnumTest.java</h3> <pre><code>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!"); } } </code></pre> <p>Also, please note that <code>Greeting</code> and <code>EnumTest</code> are in different packages. (This ends up mattering.)</p> <h1>The Error</h1> <p>When you run this code, you expect to get the following output:</p> <pre><code>Greeting class a.Greeting ... Hello! Greeted! </code></pre> <p>Instead, you get the following output:</p> <pre><code>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) </code></pre> <h1>Understanding the Behavior</h1> <p>First, please note that <code>Greeting</code> is <code>public</code> and <code>Greeting$greet</code> is <code>public</code>. (Even the error message indicates <code>public</code> access!) So what's going on?</p> <h2>What The Heck is Going On Here?</h2> <p>If you step through the code, you find that the ultimate "problem" is that <code>sun.reflect.Reflection$verifyMemberAccess()</code> returns <code>false</code>. (So, the Reflection API claims we do not have access to this method.) The particular code that fails is here:</p> <pre><code>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; } } // ... </code></pre> <p>Essentially, this method determines whether code in <code>currentClass</code> can see members of <code>memberClass</code> with modifiers of <code>modifiers</code>.</p> <p>Clearly, we should have access. We're calling a <code>public</code> method in a <code>public</code> class! However, this code returns <code>false</code>, in the indicated <code>return</code> statement. Therefore, the class of the value we're trying to invoke the method on is not <code>public</code>. (We know this because the outer test -- <code>!Modifier.isPublic(getClassAccessFlags(memberClass))</code> -- passes, since the code reaches the inner <code>return</code>.) But <code>Greeting</code> <em>is</em> <code>public</code>!</p> <p>However, the type of <code>Greeting.HELLO</code> is <em>not</em> <code>a.Greeting</code>. It's <code>a.Greeting$1</code>! (As careful readers will have noticed above.)</p> <p><code>enum</code> classes with one or more <code>abstract</code> 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 <code>public</code>, so we're not allowed to see <code>public</code> methods on those classes. Bummer.</p> <h2>Confirmation of the Theory</h2> <p>To test this theory, we can invoke the superclass <code>enum</code>'s <code>greet()</code> method on the child instead:</p> <pre><code>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!"); } </code></pre> <p>...and meet with success:</p> <pre><code>Greeting class a.Greeting$1 ... Hello! Greeted! </code></pre> <p>Also, if we move <code>a.Greeting</code> to <code>b.Greeting</code> (the same package as <code>b.EnumTest</code>), that works too, even without the <code>getSuperclass()</code> call.</p> <h1>So... Bug or No?</h1> <p>So... is this a bug? Or is this simply undesired behavior that is an artifact of the underlying implementation? I checked <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.9" rel="noreferrer">the relevant section of the Java Language Specification</a> and this syntax <em>is</em> 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.</p> <p>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.</p> <p>Also, I'm on a Mac (in case that matters), and <code>java -version</code> prints the following, for anyone who wants to reproduce:</p> <pre><code>$ 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) </code></pre> <p><strong>EDIT</strong>: Interesting to find a bug open for an similar (at least related) issue since 1997: <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4071957" rel="noreferrer">http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4071957</a></p> <p><strong>EDIT</strong>: Per the answer below, the JLS <em>does</em> say that <code>enum</code> classes with an abstract method shall behave like anonymous classes:</p> <blockquote> <p>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</p> </blockquote> <p>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. :)</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload