James Daugherty created GROOVY-12146:
----------------------------------------

             Summary: Regression of GROOVY-11715 
                 Key: GROOVY-12146
                 URL: https://issues.apache.org/jira/browse/GROOVY-12146
             Project: Groovy
          Issue Type: Bug
    Affects Versions: 5.0.7
            Reporter: James Daugherty


When a class is compiled against a precompiled trait (or any code path where an 
annotation is materialized from a loaded Class rather than from source), the 
annotation's members are written to the class file in 
Class.getDeclaredMethods() order. That order is unspecified: HotSpot sorts 
methods by internal Symbol address, so it varies across JDK builds — even patch 
releases of the same vendor — and across JVM runs with different class-loading 
histories. Identical sources therefore produce different class-file bytes in 
different environments.

The member map is populated in 
org.codehaus.groovy.vmplugin.v8.Java8#configureAnnotation(AnnotationNode, 
Annotation), which iterates type.getDeclaredMethods() (also materializing 
default values), and AsmClassGenerator#visitAnnotationAttributes then emits the 
members in map order.

Observed getDeclaredMethods() orders for groovy.lang.DelegatesTo:
- liberica 17.0.15: type,value,target,strategy,genericTypeIndex
- liberica 17.0.16: genericTypeIndex,type,value,target,strategy
- zulu 11.0.19: strategy,type,value,target,genericTypeIndex

Impact: Apache Grails 8 (Groovy 5) release artifacts fail reproducible-build 
verification because of this; every jar containing a class that implements a 
precompiled trait carrying @DelegatesTo (controllers via the Controller trait, 
GORM entities via GormEntity#withCriteria, @Delegated command classes, ...) 
differs byte-for-byte between the release build and an independent rebuild. 
Grails 7 (Groovy 4) is not affected because 4.0.x canonicalizes attribute order 
at emission (GROOVY-11715, TreeMap in visitAnnotationAttributes). That fix was 
reverted on master in 6e3737dd24 ("revert GROOVY-11715 and try a different 
approach") and no replacement landed, so 5.x emits raw reflection order.

*Reproducer: [https://github.com/jdaugherty/groovy-annotation-order-reproducer]*

compiles a trait whose method parameter carries @DelegatesTo(value = String, 
strategy = Closure.DELEGATE_FIRST, genericTypeIndex = -1), then compiles an 
implementing class against the precompiled trait. The trait's own class file 
preserves source member order (deterministic), but the woven class contains all 
five members (defaults materialized) in the JVM's reflection order. Running it 
on two different JDK builds yields different bytes for identical sources.

 



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to