Paul King created GROOVY-12544:
----------------------------------
Summary: InnerClasses attribute omits nested classes that are not
in the current compilation unit
Key: GROOVY-12544
URL: https://issues.apache.org/jira/browse/GROOVY-12544
Project: Groovy
Issue Type: Improvement
Components: Compiler
Affects Versions: 6.0.0, 5.1.3, 4.0.33
Reporter: Paul King
Fix For: 7.x
groovyc writes an {{InnerClasses}} entry for a referenced nested class only
when that class is in the same compilation unit. A nested class read from the
classpath, whether a pre-compiled Groovy class or a JDK class such as
{{Map.Entry}}, gets no entry. javac writes an entry for every nested class
referenced from the class.
h3. Reproducer
{code:groovy}
// A.groovy
class A { static class B {} }
// C.groovy
class C { def m() { new A.B() } }
{code}
{noformat}
groovyc -d together A.groovy C.groovy
groovyc -d first A.groovy
groovyc -d second -cp first C.groovy
{noformat}
{{together/C.class}} contains:
{noformat}
InnerClasses:
public static #34= #31 of #33; // B=class A$B of class A
{noformat}
{{second/C.class}} has no {{InnerClasses}} attribute. The extra constant-pool
entries in the first case also shift later indices, so instructions can change
too, e.g. {{ldc}} becomes {{ldc_w}}.
For comparison, javac compiling
{code:java}
class Y { Object m(java.util.Map.Entry e) { return new
java.util.AbstractMap.SimpleEntry(1, 2); } }
{code}
writes entries for both {{AbstractMap$SimpleEntry}} and {{Map$Entry}}. The
equivalent Groovy class has none.
h3. Why it matters
* *Reproducible output:* compiling an unchanged source file gives different
bytes depending on what else is in the compilation unit. Incremental builds
recompile a subset of sources against the previous output, so unchanged classes
come out different.
* *Downstream recompilation:* in the Gradle project from GROOVY-10932
(https://github.com/jdaugherty/groovy-trait-incremental-compile), with the
GROOVY-10932 fix applied, the missing {{InnerClasses}} entry for a trait helper
is the only remaining difference in the implementer. It is still enough to make
the downstream project recompile. A control run where the class came out
byte-identical left the downstream compile up to date. Trait helpers are only
one case: any class referencing a nested type behaves the same way.
* *Spec:* JVMS ยง4.7.6 expects every nested class referenced from the constant
pool to be listed in {{InnerClasses}}.
Runtime behaviour does not appear to be affected. Reflection on a nested class
({{getDeclaringClass}}, {{getSimpleName}}, ...) reads that class's own
{{InnerClasses}} attribute, not the attribute of the classes that reference it.
h3. Scripts
Scripts behave the same way. A script body that references a nested class gets
an {{InnerClasses}} entry only when that class is in the same compilation unit.
Classes declared alongside the script body in a script file are top-level
siblings rather than nested classes, so they need no entry and do not drift.
Classes nested inside those siblings do drift, like any other nested class. No
special handling for scripts is needed.
h3. Possible approach
* *Decompiler:* {{AsmDecompiler}} already reads a class's self-referencing
{{InnerClasses}} entry to get its modifiers. It could also keep the outer class
name and simple name in {{ClassStub}}.
* *Class generator:* {{AsmClassGenerator.maybeInnerClassEntry}} currently
writes an entry only when {{getOuterClass() != null}}, which holds only for
{{InnerClassNode}}s. It could also write one for a member class read from the
classpath, recursing through enclosing classes as {{makeInnerClassEntry}} does
today (GROOVY-9842). For class nodes built by reflection,
{{Class.getDeclaringClass()}} provides the same information.
* *Keep {{getOuterClass()}} unchanged:* it should keep returning {{null}} for
classpath classes. Name resolution, static type checking and access checks rely
on it, so the change should stay confined to bytecode generation.
h3. Compatibility
This changes the bytes of most class files that reference a nested type,
including commonly used JDK types such as {{Map.Entry}}. Behaviour is not
expected to change, but builds comparing outputs across versions will see
differences. I suggest master only, with a note in the release notes.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)