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

sunlan pushed a commit to branch GROOVY-10381
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit ab845735c910eab60fbf53794e58ef1a3ca2b295
Author: Daniel Sun <[email protected]>
AuthorDate: Sat Nov 23 23:02:24 2024 +0800

    GROOVY-10381: No compiler error for class that implements interfaces with 
duplicated default methods
---
 .../org/codehaus/groovy/classgen/Verifier.java     |  30 ++++
 src/test/groovy/bugs/Groovy10381.groovy            | 199 +++++++++++++++++++++
 2 files changed, 229 insertions(+)

diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java 
b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
index f7aeb501f2..6f2c3d338e 100644
--- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
@@ -234,6 +234,7 @@ public class Verifier implements GroovyClassVisitor, 
Opcodes {
         }
 
         checkForDuplicateInterfaces(node);
+        checkForDuplicateDefaultMethods(node);
 
         if (node.isInterface() || Traits.isTrait(node)) {
             // interfaces have no constructors but this expects one,
@@ -276,6 +277,35 @@ public class Verifier implements GroovyClassVisitor, 
Opcodes {
         checkFinalVariables(node);
     }
 
+    private static void checkForDuplicateDefaultMethods(ClassNode node) {
+        if (node.getInterfaces().length < 2) return;
+
+        Map<String, MethodNode> defaultMethods = new HashMap<>(8);
+        node.getAllInterfaces().stream()
+                .flatMap(i -> i.getAllDeclaredMethods().stream())
+                .filter(MethodNode::isDefault)
+                .forEach(m -> {
+                    String signature = methodDescriptorWithoutReturnType(m);
+                    MethodNode existing = defaultMethods.get(signature);
+                    if (existing == null) {
+                        defaultMethods.put(signature, m);
+                        return;
+                    }
+
+                    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 " 
+ m.getTypeDescriptor()
+                                        + " from types " + 
existingDeclaringClass.getName()
+                                        + " and " + 
currentDeclaringClass.getName(), sourceOf(m));
+                    }
+                });
+    }
+
     private static final String[] INVALID_COMPONENTS = {"clone", "finalize", 
"getClass", "hashCode", "notify", "notifyAll", "toString", "wait"};
 
     private static void detectInvalidRecordComponentNames(final ClassNode 
node) {
diff --git a/src/test/groovy/bugs/Groovy10381.groovy 
b/src/test/groovy/bugs/Groovy10381.groovy
new file mode 100644
index 0000000000..a49867a722
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy10381.groovy
@@ -0,0 +1,199 @@
+/*
+ *  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 bugs
+
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit
+import org.junit.Test
+
+import static groovy.test.GroovyAssert.shouldFail
+
+final class Groovy10381 {
+    @Test
+    void testDuplicateDefaultMethodsFromGroovyClasses_implements1() {
+        def err = shouldFail '''
+            interface A {
+              default void m(String n) {}
+            }
+            interface B {
+              default void m(String n) {}
+            }
+            class C implements A, B {
+              void test() {
+                m('Hi')
+              }
+            }
+        '''
+        assert err =~ /class C inherits unrelated defaults for void 
m\(java.lang.String\) from types A and B/
+    }
+
+    @Test
+    void testDuplicateDefaultMethodsFromGroovyClasses_implements2() {
+        def err = shouldFail '''
+            interface BaseA {
+              default void m(String n) {}
+            }
+            interface A extends BaseA {
+            }
+            interface BaseB {
+              default void m(String n) {}
+            }
+            interface B extends BaseB {
+            }
+            class C implements A, B {
+              void test() {
+                m('Hi')
+              }
+            }
+        '''
+        assert err =~ /class C inherits unrelated defaults for void 
m\(java.lang.String\) from types BaseA and BaseB/
+    }
+
+    @Test
+    void testDuplicateDefaultMethodsFromGroovyClasses_override() {
+        def err = shouldFail '''
+            interface BaseA {
+              default void m(String n) {}
+            }
+            interface A extends BaseA {
+              @Override
+              default void m(String n) {}
+            }
+            interface BaseB {
+              default void m(String n) {}
+            }
+            interface B extends BaseB {
+              @Override
+              default void m(String n) {}
+            }
+            class C implements A, B {
+              void test() {
+                m('Hi')
+              }
+            }
+        '''
+        assert err =~ /class C inherits unrelated defaults for void 
m\(java.lang.String\) from types A and B/
+    }
+
+    @Test
+    void testDuplicateDefaultMethodsFromGroovyClasses_extends() {
+        def err = shouldFail '''
+            public interface A extends List {
+              default void m(String n) {}
+            }
+            public interface B {
+              default void m(String n) {}
+            }
+            interface C extends A, B {
+              default void test() {
+                m('Hi')
+              }
+            }
+        '''
+        assert err =~ /interface C inherits unrelated defaults for void 
m\(java.lang.String\) from types A and B/
+    }
+
+    @Test
+    void testDuplicateDefaultMethodsFromJavaClasses_implements() {
+        def config = new CompilerConfiguration().tap {
+            jointCompilationOptions = [memStub: true]
+            targetDirectory = File.createTempDir()
+        }
+        File parentDir = File.createTempDir()
+        try {
+            def a = new File(parentDir, 'A.java')
+            a.write '''
+                public interface A {
+                    default void m(String n) {}
+                }
+            '''
+
+            def b = new File(parentDir, 'B.java')
+            b.write '''
+                public interface B {
+                    default void m(String n) {}
+                }
+            '''
+
+            def c = new File(parentDir, 'C.groovy')
+            c.write '''
+                class C implements A, B {
+                    void test() {
+                        m("test")
+                    }
+                }
+            '''
+
+            def loader = new GroovyClassLoader(this.class.classLoader)
+            def cu = new JavaAwareCompilationUnit(config, loader)
+            cu.addSources(a, b, c)
+            cu.compile()
+            assert false
+        } catch (Exception e) {
+            assert e =~ /class C inherits unrelated defaults for m\(String\) 
from types A and B/
+        } finally {
+            parentDir.deleteDir()
+            config.targetDirectory.deleteDir()
+        }
+    }
+
+    @Test
+    void testDuplicateDefaultMethodsFromJavaClasses_extends() {
+        def config = new CompilerConfiguration().tap {
+            jointCompilationOptions = [memStub: true]
+            targetDirectory = File.createTempDir()
+        }
+        File parentDir = File.createTempDir()
+        try {
+            def a = new File(parentDir, 'A.java')
+            a.write '''
+                public interface A extends java.util.List {
+                    default void m(String n) {}
+                }
+            '''
+
+            def b = new File(parentDir, 'B.java')
+            b.write '''
+                public interface B {
+                    default void m(String n) {}
+                }
+            '''
+
+            def c = new File(parentDir, 'C.groovy')
+            c.write '''
+                interface C extends A, B {
+                    default void test() {
+                        m("test")
+                    }
+                }
+            '''
+
+            def loader = new GroovyClassLoader(this.class.classLoader)
+            def cu = new JavaAwareCompilationUnit(config, loader)
+            cu.addSources(a, b, c)
+            cu.compile()
+            assert false
+        } catch (Exception e) {
+            assert e =~ /interface C inherits unrelated defaults for 
m\(String\) from types A and B/
+        } finally {
+            parentDir.deleteDir()
+            config.targetDirectory.deleteDir()
+        }
+    }
+}

Reply via email to