JDK-8170848 : TreePathScanner.visitClass is not called for new class statements of classes without a body
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 8u112
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: os_x
  • CPU: x86
  • Submitted: 2016-12-07
  • Updated: 2017-03-10
  • Resolved: 2016-12-07
Description
FULL PRODUCT VERSION :


A DESCRIPTION OF THE PROBLEM :
When using TreePathScanner, visitClass is not called for new class statements without a body. For example, given the following source code:

public class InnerClass {
    private final InnerInnerClass clazz = new InnerInnerClass();

    private class InnerInnerClass {
    }
}

three class files would be generated - InnerClass.class, InnerClass$1.class, and InnerClass$InnerInnerClass.class

However, onVisit is only called twice, for the InnerClass class, and the InnerInnerClass classes, but not the anonymously named class created from the new statement. Changing the source to the following works as expected:

public class InnerClass {
    private final InnerInnerClass clazz = new InnerInnerClass() { };

    private class InnerInnerClass {
    }
}

Either this is a bug or a flaw in the API as there doesn't seem to be any other way to get all of the binary names that will be generated from a given compilation unit's source code.


REPRODUCIBILITY :
This bug can be reproduced always.


Comments
I acknowledge that 3 class files are generated, but one is a code-generation artifact of the back end, and is not known about when the AST is being scanned. As far as the TreePathScanner is concerned, javac's behavior is correct: the AST just describes two classes, InnerClass, and InnerClass.InnerInnerClass, and so the TreePathScanner is correct for only calling visitClass twice. So where does the extra class come from? After the AST is analyzed, javac works to rewrite the AST, into simpler constructions. This involves replacing many of the more recent language features (strings in switch, for-each loops, lambda expressions, etc) with simpler Java code, before finally generating class files. Sometimes, this rewriting may involve generating additional classes, and that is the case here. It is not appropriate for the tree scanner to know about these extra classes, just as it would not be appropriate for it to know about methods generated for lambda expressions and other features. In this case, it is not the "new class" statement that is triggering the extra class being generated; it is the fact that InnerInnerClass is declared private. Look at this: $ /opt/jdk/1.9.0/bin/javac -d play/8170848/classes play/8170848/src/InnerClass.java && find play/8170848 -name \*.class play/8170848/classes/InnerClass.class play/8170848/classes/InnerClass$InnerInnerClass.class $ more play/8170848/src/InnerClass.javapublic class InnerClass { private final InnerInnerClass clazz = new InnerInnerClass(); public class InnerInnerClass { } } $ /opt/jdk/1.9.0/bin/javac -d play/8170848/classes play/8170848/src/InnerClass.java $ find play/8170848/ -name \*.class play/8170848/classes/InnerClass.class play/8170848/classes/InnerClass$InnerInnerClass.class Change that InnerInnerClass to be public, and the InnerClass$1 goes away. The reason is that javac generates a special hidden class to help ensure that the nested class stays "private" -- the JVM doesn't support private classes, so javac uses a private key to ensure that no one else can create instances of that "private" nested class. Why does modifying the new class statement make the problem go away? When you modify the new statement, you are explicitly creating a third class, so visitClass will indeed be called 3 times. The extra class is "good enough" for javac to be able to use when protecting access to the private nested class, and so no additional class is generated. So, to summarize: * yes, a third class is generated by the javac backend * yes, the TreePathScanner will just visit the two classes present in the source code and AST * yes, javac is correct, and there is no flaw in either javac or the API in this regard So where does that leave you, if you want to find the binary names of all the class files that are generated? The bottom line is that the TreePathScanner is the wrong place to start. You're already using the Compiler Tree API (i.e. com.sun.source.** classes) so check out com.sun.source.util.TaskListener, TaskEvent, and TaskEvent.Kind, and JavaFileManager.inferBinaryName https://docs.oracle.com/javase/8/docs/jdk/api/javac/tree/com/sun/source/util/TaskListener.html https://docs.oracle.com/javase/8/docs/jdk/api/javac/tree/com/sun/source/util/TaskEvent.html https://docs.oracle.com/javase/8/docs/jdk/api/javac/tree/com/sun/source/util/TaskEvent.Kind.html#GENERATE https://docs.oracle.com/javase/8/docs/api/javax/tools/JavaFileManager.html#inferBinaryName-javax.tools.JavaFileManager.Location-javax.tools.JavaFileObject- With this API, you can be notified when class files are being generated, and that should give you the information you are looking for.
01-03-2017

As written, the new statement does not (should not) generate a new inner class, and so javac's behavior is correct.
07-12-2016

Issue looks duplicate of JDK-6595115 which is still in open state.
07-12-2016

Issue is more specific to corelibs
07-12-2016