This is an automated email from the ASF dual-hosted git repository.
sunlan 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 4f7fb0fc59 GROOVY-10381: No compiler error for class that implements
interfaces with duplicated default methods
4f7fb0fc59 is described below
commit 4f7fb0fc5950395954972dcd870c8cda612c9ec6
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()
+ }
+ }
+}