JDK-8129576 : Anonymous classes are not final according to reflection API
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 7u51,8u60,9
  • Priority: P5
  • Status: Resolved
  • Resolution: Not an Issue
  • Submitted: 2015-06-23
  • Updated: 2017-05-19
  • Resolved: 2016-07-07
Related Reports
Blocks :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
if I understand correctly according to following assertion from JLS 15.9.5 anonymous classes are always final:

    An anonymous class is always implicitly final (��8.1.1.2).

But reflection API reports that the class is not final. Namely let's consider following code:

    import java.lang.reflect.Modifier;

    public class Test12 {
        static class Foo<T> {}

        public static void main(String argv[]) {
            Foo<Integer> foo = new Foo<>() {};
            if ( (foo.getClass().getModifiers() & Modifier.FINAL) != 0 ) {
                System.out.println("final, modifiers: " + foo.getClass().getModifiers());
            } else {
                System.out.println("not final, modifiers: " + foo.getClass().getModifiers());
            }
        }
    }

On JDK9b69 it reports:

    not final, modifiers: 0

javap reports:
final class Test12$1 extends Test12$Foo<java.lang.Integer>
  minor version: 0
  major version: 52
  flags: ACC_FINAL, ACC_SUPER

This seems to be a discrepancy between spec and javac (VM? reflection API?) which should be fixed.
Comments
After pushing hard on other alternatives that I've ultimately deemed unacceptably disruptive, I think the best resolution is to just change the specification so that there is no assertion that anonymous classes are 'final'. This is a bug fix, to align JLS with longstanding platform behavior. See JDK-8161009.
07-07-2016

Note that there are *two* places where ACC_FINAL is set in class files, and javac should be consistently setting them both. 1) The class's flags 2) The flags in the InnerClasses attribute The reflection API relies on (2), but (1) needs to be fixed, too.
16-06-2016

Proposed solution is to update serialization to eliminate the compatibility problem -- JDK-8159751 -- then correct javac behavior.
16-06-2016

ILW prioritization: Impact=medium (no evidence that end users care about this, but it is a conformance failure) Likelihood=low (requires doing reflection on anonymous classes, which is unusual) Workaround=low (logic that depends on this can use 'isAnonymousClass' to detect it) --> P5
16-06-2016

Discussion thread on compiler-dev: http://mail.openjdk.java.net/pipermail/compiler-dev/2015-June/009613.html
15-06-2016

@Karen - yeah for the use of Jallie!
23-02-2016

The word "implicitly" in "implicitly final" means the anonymous classes will not have their final bit set. There is a long compatibility history here. tl;dr if the final bit were set, there would be serialization incompatibilities. See bugs JDK-6520152:"ACC_FINAL flag for anonymous classes shouldn't be set" and JDK-4777101 "final treatment of anonymous classes not marked in .class files?" for details.
23-02-2016

Thank you for the small test case. Giving the bug to the langtools folks for javac, since we need the InnerClasses_attribute information to be complete. It would also be nice if javap were to report the full InnerClasses_attribute information when used with -v. I ran Test12 and used jallie (http://jallie.sourceforge.net) to look in more detail at the generated classfiles and their attributes. For inner classes, the jvm returns the access flags that are in the InnerClasses_attribute of the current classfile, not the classfile flags value, since member classes have an extended valid flag set. The InnerClasses_attribute for Test12$Foo.class >>> print cf.attributes[2] InnerClasses_attribute { u2 attribute_name_index = #16 // "InnerClasses" u4 attribute_length = 10 u2 number_of_classes = 1 classes_entry classes[1] = { ... } } >>> print cf.attributes[2].classes[0] classes_entry { u2 inner_class_info_index = #2 // "Test12$Foo" u2 outer_class_info_index = #13 // "Test12" u2 inner_name_index = #15 // "Foo" u2 inner_class_access_flags = ( ACC_STATIC ) } ==== The InnerClasses_attribute of Test12$1.class >>> print cf.attributes[3].classes[0] classes_entry { u2 inner_class_info_index = #3 // "Test12$Foo" u2 outer_class_info_index = #15 // "Test12" u2 inner_name_index = #9 // "Foo" u2 inner_class_access_flags = ( ACC_STATIC ) } >>> print cf.attributes[3].classes[1] classes_entry { u2 inner_class_info_index = #2 // "Test12$1" u2 outer_class_info_index = #0 // NULL u2 inner_name_index = #0 // NULL u2 inner_class_access_flags = ( ) } ==== The InnerClasses_attribute of Test12.class >>> print cf.attributes[1] InnerClasses_attribute { u2 attribute_name_index = #20 // "InnerClasses" u4 attribute_length = 18 u2 number_of_classes = 2 classes_entry classes[2] = { ... } } >>> print cf.attributes[1].classes[0] classes_entry { u2 inner_class_info_index = #18 // "Test12$Foo" u2 outer_class_info_index = #16 // "Test12" u2 inner_name_index = #19 // "Foo" u2 inner_class_access_flags = ( ACC_STATIC ) } >>> print cf.attributes[1].classes[1] classes_entry { u2 inner_class_info_index = #2 // "Test12$1" u2 outer_class_info_index = #0 // NULL u2 inner_name_index = #0 // NULL u2 inner_class_access_flags = ( ) }
22-02-2016