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)

Reply via email to