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'] + } +}
