[
https://issues.apache.org/jira/browse/GROOVY-12544?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18134079#comment-18134079
]
ASF GitHub Bot commented on GROOVY-12544:
-----------------------------------------
paulk-asert opened a new pull request, #3077:
URL: https://github.com/apache/groovy/pull/3077
…t in the current compilation unit
A class that refers to a nested class got an InnerClasses entry for it only
when the nested class was in the same compilation unit. A nested class read
from the class path, whether a pre-compiled Groovy class or a JDK class, got no
entry. The same source therefore compiled to different bytes depending on what
else was in the compilation unit, which defeats compile avoidance in
incremental builds.
When reading a class file, the decompiler now keeps the class's own
InnerClasses entries for itself and its enclosing classes, and those entries
are written into any class that refers to it. For class nodes built by
reflection, the same entries are derived from the declaring classes.
getOuterClass() is unchanged, so only the generated bytecode is affected.
> 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
> Priority: Major
> 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)