You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2026/08/16 11:50:18 UTC
(groovy) 01/01: GROOVY-12264: Optimize the unrelated-default-method scan during class generation
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch GROOVY-12264
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 4f5ea608bcfd2ecd7a01fe80eb0d7e96ab7f78d3
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sun Aug 16 20:30:19 2026 +0900
GROOVY-12264: Optimize the unrelated-default-method scan during class generation
---
.../java/org/codehaus/groovy/ast/MethodNode.java | 5 +-
.../org/codehaus/groovy/classgen/Verifier.java | 90 ++++++++++++++--------
src/test/groovy/bugs/Groovy10381.groovy | 72 +++++++++++++++++
3 files changed, 132 insertions(+), 35 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/ast/MethodNode.java b/src/main/java/org/codehaus/groovy/ast/MethodNode.java
index 13497d9fdb..51c6e068de 100644
--- a/src/main/java/org/codehaus/groovy/ast/MethodNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/MethodNode.java
@@ -23,7 +23,6 @@ import org.codehaus.groovy.ast.stmt.Statement;
import java.lang.reflect.Modifier;
import java.util.List;
-import java.util.Optional;
import static org.apache.groovy.ast.tools.ClassNodeUtils.formatTypeName;
import static org.apache.groovy.ast.tools.MethodNodeUtils.methodDescriptor;
@@ -284,8 +283,8 @@ public class MethodNode extends AnnotatedNode {
* @return true if this method is a default interface method
*/
public boolean isDefault() {
- return (modifiers & (ACC_ABSTRACT | ACC_PUBLIC | ACC_STATIC)) == ACC_PUBLIC &&
- Optional.ofNullable(getDeclaringClass()).filter(ClassNode::isInterface).isPresent();
+ return (modifiers & (ACC_ABSTRACT | ACC_PUBLIC | ACC_STATIC)) == ACC_PUBLIC
+ && getDeclaringClass() != null && getDeclaringClass().isInterface();
}
/**
diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
index 1faac2d52a..a88ad087e7 100644
--- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
@@ -88,7 +88,6 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.IntFunction;
-import java.util.stream.Collectors;
import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isPrivate;
@@ -318,40 +317,67 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
checkFinalVariables(node);
}
- private static void checkForDuplicateDefaultMethods(ClassNode node) {
+ /**
+ * Rejects unrelated inherited default methods (JLS 8.4.8 / 9.4.1).
+ * Uses each interface's own methods; {@code getAllDeclaredMethods()} would
+ * rebuild a hierarchy map per type and revisit inherited defaults.
+ */
+ private static void checkForDuplicateDefaultMethods(final ClassNode node) {
if (node.getInterfaces().length < 2) return;
- Map<String, MethodNode> defaultMethods = new HashMap<>(8);
- Set<String> declared = node.getAllDeclaredMethods().stream()
- .filter(m -> !m.isDefault())
- .map(MethodNodeUtils::methodDescriptorWithoutReturnType)
- .collect(Collectors.toSet());
- node.getAllInterfaces().stream()
- .flatMap(i -> i.getAllDeclaredMethods().stream())
- .filter(MethodNode::isDefault)
- .forEach(m -> {
- String signature = MethodNodeUtils.methodDescriptorWithoutReturnType(m);
- if (declared.contains(signature)) {
- return;
- }
- MethodNode existing = defaultMethods.get(signature);
- if (existing == null) {
- defaultMethods.put(signature, m);
- return;
- }
+ Map<String, MethodNode> defaultMethods = null;
+ Set<String> declared = null;
+ for (ClassNode iface : node.getAllInterfaces()) {
+ for (MethodNode method : iface.getMethods()) {
+ if (!method.isDefault()) continue;
- ClassNode existingDeclaringClass = existing.getDeclaringClass();
- ClassNode currentDeclaringClass = m.getDeclaringClass();
- if (!(existingDeclaringClass.equals(currentDeclaringClass)
- || existingDeclaringClass.implementsInterface(currentDeclaringClass)
- || currentDeclaringClass.implementsInterface(existingDeclaringClass))) {
- throw new RuntimeParserException(
- (node.isInterface() ? "interface" : "class") + " " + node.getName()
- + " inherits unrelated defaults for " + MethodNodeUtils.methodDescriptor(m, true)
- + " from types " + existingDeclaringClass.getName()
- + " and " + currentDeclaringClass.getName(), node);
- }
- });
+ String signature = MethodNodeUtils.methodDescriptorWithoutReturnType(method);
+ if (declared != null && declared.contains(signature)) continue;
+
+ if (defaultMethods == null) {
+ defaultMethods = new HashMap<>(8);
+ }
+ MethodNode existing = defaultMethods.putIfAbsent(signature, method);
+ if (existing == null) continue;
+
+ ClassNode existingDeclaringClass = existing.getDeclaringClass();
+ ClassNode currentDeclaringClass = method.getDeclaringClass();
+ if (existingDeclaringClass.equals(currentDeclaringClass)
+ || existingDeclaringClass.implementsInterface(currentDeclaringClass)
+ || currentDeclaringClass.implementsInterface(existingDeclaringClass)) {
+ continue;
+ }
+
+ if (declared == null) {
+ declared = collectNonDefaultMethodSignatures(node);
+ if (declared.contains(signature)) continue;
+ }
+
+ throw new RuntimeParserException(
+ (node.isInterface() ? "interface" : "class") + " " + node.getName()
+ + " inherits unrelated defaults for " + MethodNodeUtils.methodDescriptor(method, true)
+ + " from types " + existingDeclaringClass.getName()
+ + " and " + currentDeclaringClass.getName(), node);
+ }
+ }
+ }
+
+ /**
+ * Signatures of methods declared on {@code node} or a superclass. These hide
+ * inherited interface defaults; abstracts on other interfaces do not.
+ */
+ private static Set<String> collectNonDefaultMethodSignatures(final ClassNode node) {
+ Set<String> declared = null;
+ for (ClassNode cn = node; cn != null && !isObjectType(cn); cn = cn.getSuperClass()) {
+ for (MethodNode method : cn.getMethods()) {
+ if (method.isDefault()) continue;
+ if (declared == null) {
+ declared = new HashSet<>();
+ }
+ declared.add(MethodNodeUtils.methodDescriptorWithoutReturnType(method));
+ }
+ }
+ return declared == null ? Collections.emptySet() : declared;
}
private static final String[] INVALID_COMPONENTS = {"clone", "finalize", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait"};
diff --git a/src/test/groovy/bugs/Groovy10381.groovy b/src/test/groovy/bugs/Groovy10381.groovy
index 2190d74014..e6e852d01e 100644
--- a/src/test/groovy/bugs/Groovy10381.groovy
+++ b/src/test/groovy/bugs/Groovy10381.groovy
@@ -58,6 +58,78 @@ final class Groovy10381 {
'''
}
+ @Test
+ void testDuplicateDefaultMethods_superclassProvidesMethod() {
+ assertScript '''
+ interface A {
+ default String m(String n) { n.toLowerCase() }
+ }
+ interface B {
+ default String m(String n) { n.toUpperCase() }
+ }
+ class Super {
+ String m(String n) { n }
+ }
+ class C extends Super implements A, B {
+ }
+ assert new C().m('Hi') == 'Hi'
+ '''
+ }
+
+ @Test
+ void testDuplicateDefaultMethods_interfaceProvidesOwnDefault() {
+ assertScript '''
+ interface A {
+ default String m(String n) { n.toLowerCase() }
+ }
+ interface B {
+ default String m(String n) { n.toUpperCase() }
+ }
+ interface C extends A, B {
+ default String m(String n) { n }
+ }
+ class D implements C {
+ }
+ assert new D().m('Hi') == 'Hi'
+ '''
+ }
+
+ @Test
+ void testDuplicateDefaultMethods_moreSpecificDefaultWins() {
+ assertScript '''
+ interface Base {
+ default String m(String n) { n }
+ }
+ interface A extends Base {
+ default String m(String n) { n.toLowerCase() }
+ }
+ interface B extends Base {
+ }
+ class C implements A, B {
+ }
+ assert new C().m('Hi') == 'hi'
+ '''
+ }
+
+ @Test
+ void testDuplicateDefaultMethods_interfaceRedeclaresAbstract() {
+ assertScript '''
+ interface A {
+ default String m(String n) { n.toLowerCase() }
+ }
+ interface B {
+ default String m(String n) { n.toUpperCase() }
+ }
+ interface C extends A, B {
+ String m(String n)
+ }
+ class D implements C {
+ String m(String n) { n }
+ }
+ assert new D().m('Hi') == 'Hi'
+ '''
+ }
+
@Test
void testDuplicateDefaultMethodsFromGroovyClasses_implements1() {
def err = shouldFail '''