Search code examples
javareflectionannotation-processingannotation-processor

why getSimpleName() is twice in com.sun.tools.javac.tree.JCTree$JCClassDecl


I had a weird bug in an application code, which is an annotation processor and I could find that the root cause of the bug was that the class com.sun.tools.javac.tree.JCTree$JCClassDecl contains the method getSimpleName() twice when I query the class using the reflective method getMethods(). The two versions differ only in the return type. This is legal in JVM code, but not legal in Java. This is not method overloading, because it is only the return type that differs and the return type is not part of the method signature.

The issue can be demonstrated with the simple code:

Method[] methods = com.sun.tools.javac.tree.JCTree.JCClassDecl.class.getMethods();
for (int i = 0; i < methods.length; i++) {
    System.out.println(methods[i]);
}

It will print

...
public javax.lang.model.element.Name com.sun.tools.javac.tree.JCTree$JCClassDecl.getSimpleName()
public com.sun.tools.javac.util.Name com.sun.tools.javac.tree.JCTree$JCClassDecl.getSimpleName()
...

(The ellipsis stands for more output lines showing the various other methods that are not interesting for us now.)

The Java version I used to test this is

$ java -version
java version "11" 2018-09-25
Java(TM) SE Runtime Environment 18.9 (build 11+28)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28, mixed mode)

on a Windows 10 machine.

QUESTION: How was this class code created? My understanding is that this part of the code is written in Java, but in Java this is not possible. Also: what is the aim to have two same-signature versions of a method? Any hint?


Solution

  • If you look at the source code1 you'll see there's only one method with a name of getSimpleName(). This method returns com.sun.tools.javac.util.Name. There's two critical things to note about this:

    1. That method is actually overriding com.sun.source.tree.ClassTree#getSimpleName() which is declared to return javax.lang.model.element.Name.
    2. The com.sun.tools.javac.util.Name abstract class implements the javax.lang.model.element.Name interface, and since the overridden method returns the former it is taking advantage of covariant return types.

    According to this Oracle blog, a method which overrides another but declares a covariant return type is implemented using bridge methods.

    How is this implemented?

    Although the return type based overloading is not allowed by java language, JVM always allowed return type based overloading. JVM uses full signature of a method for lookup/resolution. Full signature includes return type in addition to argument types. i.e., a class can have two or more methods differing only by return type. javac uses this fact to implement covariant return types. In the above, CircleFactory example, javac generates code which is equivalent to the following:

    class CircleFactory extends ShapeFactory {
        public Circle newShape() {
           // your code from the source file
           return new Circle();
        }
        // javac generated method in the .class file
        public Shape newShape() {
           // call the other newShape method here -- invokevirtual newShape:()LCircle;
        }  
    }
    

    We can use javap with -c option on the class to verify this. Note that we still can't use return type based overloading in source language. But, this is used by javac to support covariant return types. This way, there is no change needed in the JVM to support covariant return types.

    And in fact, if you run the following command:

    javap -v com.sun.tools.javac.tree.JCTree$JCClassDecl
    

    The following will be output (only including the relevant methods):

    public com.sun.tools.javac.util.Name getSimpleName();
    descriptor: ()Lcom/sun/tools/javac/util/Name;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #13                 // Field name:Lcom/sun/tools/javac/util/Name;
         4: areturn
      LineNumberTable:
        line 801: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/sun/tools/javac/tree/JCTree$JCClassDecl;
    

    And:

    public javax.lang.model.element.Name getSimpleName();
    descriptor: ()Ljavax/lang/model/element/Name;
    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #96                 // Method getSimpleName:()Lcom/sun/tools/javac/util/Name;
         4: areturn
      LineNumberTable:
        line 752: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/sun/tools/javac/tree/JCTree$JCClassDecl;
    

    As you can see, the second method, the one which returns javax.lang.model.element.Name, is both synthetic and a bridge. In other words, the method is generated by the compiler as part of the implementation of covariant return types. It also simply delegates to the "real" method, the one actually present in the source code which returns com.sun.tools.javac.util.Name.


    1. The source code link is for JDK 13.