[
https://issues.apache.org/jira/browse/GROOVY-12158?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18095807#comment-18095807
]
ASF GitHub Bot commented on GROOVY-12158:
-----------------------------------------
paulk-asert opened a new pull request, #2703:
URL: https://github.com/apache/groovy/pull/2703
… order they are found
> @AutoImplement generates methods in hash order, not the order they are found
> ----------------------------------------------------------------------------
>
> Key: GROOVY-12158
> URL: https://issues.apache.org/jira/browse/GROOVY-12158
> Project: Groovy
> Issue Type: Improvement
> Components: AST Transforms
> Reporter: Paul King
> Assignee: Paul King
> Priority: Major
>
> h2. Description
> {{AutoImplementASTTransformation.getAllCorrectedMethodsMap}} collects the
> methods that need implementing into an unordered map, and the callers iterate
> its values to generate members:
> {code:java}
> // AutoImplementASTTransformation.java:157-161
> static Map<String, MethodNode> getAllCorrectedMethodsMap(final ClassNode
> cNode) {
> Map<String, MethodNode> result = new HashMap<>();
> for (MethodNode mn : getMethodsWithGenerated(cNode)) {
> result.put(methodDescriptorWithoutReturnType(mn), mn);
> }
> ...
> }
> // AutoImplementASTTransformation.java:110
> for (MethodNode candidate : getAllCorrectedMethodsMap(cNode).values()) {
> if (candidate.isAbstract()) {
> MethodNode mNode = addGeneratedMethod(cNode, ...); // <-- emits
> into the class file
> {code}
> The keys are method descriptors, so *the generated methods are emitted in the
> hash order of those descriptors* rather than in the order the methods were
> discovered. Two consumers iterate this map, so both are affected:
> * {{AutoImplementASTTransformation.createMethods}} ({{:110}}) — the generated
> methods in the class file.
> * {{AutoImplementASTStubber}} ({{:75}}) — the placeholder methods in
> joint-compilation Java stubs.
> h3. Demonstration
> {code:java}
> interface Face {
> void alpha()
> void beta()
> void gamma()
> void delta()
> void epsilon()
> void zeta()
> void eta()
> void theta()
> }
> @groovy.transform.AutoImplement
> class Impl implements Face {}
> {code}
> Reading the emitted {{Impl.class}} with an ASM {{ClassReader}}, the generated
> methods appear as:
> {noformat}
> delta, zeta, eta, theta, alpha, epsilon, beta, gamma
> {noformat}
> rather than the declared order {{alpha, beta, gamma, delta, epsilon, zeta,
> eta, theta}}.
> h3. Impact
> The keys are {{String}} values and {{String.hashCode}} is stable, so for a
> given key set the iteration order is reproducible across JVM runs — this is
> not by itself a live reproducible-builds failure. It still matters for two
> reasons:
> * The *insertion* order into the map comes from {{ClassNode.getMethods()}} of
> the class and its supertypes, which for reflection-configured {{ClassNode}}s
> is the unspecified JDK reflection order being addressed in GROOVY-12149.
> Within a colliding bucket, {{HashMap}} chain order follows insertion order,
> so the emitted method order is coupled to that nondeterminism.
> * Even where it is stable, the emitted order is arbitrary rather than
> meaningful, which makes generated class files and generated stubs harder to
> diff and reason about.
> h3. Proposed fix
> Use an insertion-ordered map:
> {code:java}
> Map<String, MethodNode> result = new LinkedHashMap<>();
> {code}
> The generated methods are then emitted in the order they are found
> (declaration order in the example above). The method *set* is unchanged —
> only the order.
> Note {{LinkedHashMap.put}} on an existing key retains the original insertion
> position, so the walk's replacement of weaker candidates with stronger ones
> from supertypes ({{:172-173}}, {{:191-192}}) keeps each method where it was
> first seen. {{updatedGenericsSpec}} ({{:179}}) is a lookup-only map and does
> not need to change.
> h3. Test
> {{src/test/groovy/org/codehaus/groovy/transform/AutoImplementMethodOrderTest.groovy}}
> compiles the source above via {{CompilationUnit}}, reads the emitted bytes
> with an ASM {{ClassReader}}, and asserts the generated methods appear in
> declaration order. It fails on the current code with the scrambled order
> shown above and passes with the fix.
> h3. Related
> GROOVY-12149 (nondeterministic reflection order flows into generated
> bytecode) — feeds the insertion order of this map. Same class of defect:
> {{Verifier.addCovariantMethods}} (covariant bridge methods emitted in hash
> order), {{AsmClassGenerator}} {{referencedClasses}} ({{$class$}} synthetic
> fields), {{AnnotationCollectorTransform.makeListOfAnnotations}} (annotation
> member order for precompiled collectors), and GROOVY-12156
> ({{chooseBestMethod}} candidates in identity-hash order). These could
> reasonably be folded into a single issue.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)