[ 
https://issues.apache.org/jira/browse/GROOVY-12544?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Paul King updated GROOVY-12544:
-------------------------------
    Fix Version/s: 7.0.0-alpha-1
                   6.0.1
                       (was: 7.x)

> 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: 4.0.33, 5.1.3, 6.0.0
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>             Fix For: 7.0.0-alpha-1, 6.0.1
>
>
> 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