This is an automated email from the ASF dual-hosted git repository.

paulk-asert pushed a commit to branch GROOVY_5_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit e9b08dbff9d2e16057764f336674bfed1b6513ff
Author: Paul King <[email protected]>
AuthorDate: Mon Jul 13 16:43:57 2026 +1000

    GROOVY-12157: Covariant bridge methods are emitted in hash order, not the 
order they are found
---
 .../org/codehaus/groovy/classgen/Verifier.java     | 10 +++-
 .../classgen/CovariantBridgeMethodOrderTest.groovy | 69 ++++++++++++++++++++++
 2 files changed, 76 insertions(+), 3 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java 
b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
index 7ce43d5b48..bfa35e85bf 100644
--- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
@@ -80,6 +80,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -1433,8 +1434,11 @@ public class Verifier implements GroovyClassVisitor, 
Opcodes {
     }
 
     protected void addCovariantMethods(final ClassNode classNode) {
-        Map<String, MethodNode> absInterfaceMethods = new HashMap<>();
-        Map<String, MethodNode> allInterfaceMethods = new HashMap<>();
+        // insertion-ordered: these maps are iterated to decide which bridge 
methods
+        // to add and in what order, so hash order would put the generated 
methods in
+        // an arbitrary order in the class file rather than the order they are 
found
+        Map<String, MethodNode> absInterfaceMethods = new LinkedHashMap<>();
+        Map<String, MethodNode> allInterfaceMethods = new LinkedHashMap<>();
         Set<ClassNode> allInterfaces = getAllInterfaces(classNode);
         allInterfaces.remove(classNode);
 
@@ -1473,7 +1477,7 @@ public class Verifier implements GroovyClassVisitor, 
Opcodes {
             }
         }
 
-        Map<String, MethodNode> methodsToAdd = new HashMap<>();
+        Map<String, MethodNode> methodsToAdd = new LinkedHashMap<>();
         Map<String, ClassNode > genericsSpec = Collections.emptyMap();
         addCovariantMethods(classNode, declaredMethods, absInterfaceMethods, 
methodsToAdd, genericsSpec);
 
diff --git 
a/src/test/groovy/org/codehaus/groovy/classgen/CovariantBridgeMethodOrderTest.groovy
 
b/src/test/groovy/org/codehaus/groovy/classgen/CovariantBridgeMethodOrderTest.groovy
new file mode 100644
index 0000000000..64298d51d4
--- /dev/null
+++ 
b/src/test/groovy/org/codehaus/groovy/classgen/CovariantBridgeMethodOrderTest.groovy
@@ -0,0 +1,69 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.classgen
+
+import org.codehaus.groovy.control.CompilationUnit
+import org.codehaus.groovy.control.Phases
+import org.junit.jupiter.api.Test
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+
+final class CovariantBridgeMethodOrderTest {
+
+    /**
+     * The covariant bridge methods Verifier adds must be emitted in the order 
they are
+     * found, not in the hash order of the map that collects them, so that 
identical
+     * sources produce identical class files.
+     */
+    @Test
+    void testBridgeMethodsAreEmittedInDeclarationOrder() {
+        def cu = new CompilationUnit()
+        cu.addSource('Sample.groovy', '''
+            interface Alpha   { Object alpha()   }
+            interface Beta    { Object beta()    }
+            interface Gamma   { Object gamma()   }
+            interface Delta   { Object delta()   }
+            interface Epsilon { Object epsilon() }
+
+            class Sample implements Alpha, Beta, Gamma, Delta, Epsilon {
+                String  alpha()   { null }
+                Integer beta()    { null }
+                Long    gamma()   { null }
+                Double  delta()   { null }
+                Short   epsilon() { null }
+            }
+        ''')
+        cu.compile(Phases.CLASS_GENERATION)
+
+        byte[] bytes = cu.classes.find { it.name == 'Sample' }.bytes
+
+        def bridges = []
+        new ClassReader(bytes).accept(new ClassVisitor(Opcodes.ASM9) {
+            @Override
+            MethodVisitor visitMethod(int access, String name, String 
descriptor, String signature, String[] exceptions) {
+                if (descriptor == '()Ljava/lang/Object;') bridges << name
+                return null
+            }
+        }, 0)
+
+        assert bridges == ['alpha', 'beta', 'gamma', 'delta', 'epsilon']
+    }
+}

Reply via email to