[
https://issues.apache.org/jira/browse/GROOVY-12146?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18095605#comment-18095605
]
ASF GitHub Bot commented on GROOVY-12146:
-----------------------------------------
jdaugherty opened a new pull request, #2690:
URL: https://github.com/apache/groovy/pull/2690
Code changes:
- Added ReflectionUtils#getDeclaredMethodsSorted(Class),
#getDeclaredFieldsSorted(Class), and #getDeclaredConstructorsSorted(Class):
deterministic-order variants of the JDK reflection calls (methods sorted by
name then signature, fields by name, constructors by signature), with javadoc
documenting the nondeterminism and why callers whose output feeds bytecode
generation must use them.
- Java8#configureClassNode now uses the sorted variants for methods, fields,
and constructors, so member order in reflection-backed ClassNodes — and
everything derived from it — is deterministic.
- Refactored the GROOVY-12146 fix in Java8#configureAnnotation to reuse
getDeclaredMethodsSorted instead of its inline sort, centralizing the sorting
so future callers don't repeat it ad hoc.
Sorting is behavior-neutral: member order carries no semantic meaning in
these paths; it only affects the byte layout of emitted class files.
> Regression of GROOVY-11715 - Unordered Annotation members
> ----------------------------------------------------------
>
> 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
> Assignee: Paul King
> Priority: Major
> Fix For: 5.0.8, 6.0.0-beta-1
>
>
> 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)