This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new 2ab10f94a2 GROOVY-12157: Covariant bridge methods are emitted in hash
order, not the order they are found
2ab10f94a2 is described below
commit 2ab10f94a23d202272e68336c663b60115d81714
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 5efbf0ab00..1faac2d52a 100644
--- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
@@ -81,6 +81,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;
@@ -1676,8 +1677,11 @@ public class Verifier implements GroovyClassVisitor,
Opcodes {
* @param classNode the class being enhanced
*/
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);
@@ -1716,7 +1720,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']
+ }
+}