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)