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 2024/11/23 15:03:18 UTC
(groovy) 01/01: GROOVY-10381: No compiler error for class that implements interfaces with duplicated default methods
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 a8df5dba145048ace0df94136a9486e581e7230d
Author: Daniel Sun <su...@apache.org>
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 | 22 +++
src/test/groovy/bugs/Groovy10381.groovy | 173 +++++++++++++++++++++
2 files changed, 195 insertions(+)
diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
index 93b766ffbb..f60f7bc9c7 100644
--- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
@@ -232,6 +232,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,
@@ -275,6 +276,27 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
checkFinalVariables(node);
}
+ private static void checkForDuplicateDefaultMethods(ClassNode node) {
+ Map<String, MethodNode> defaultMethods = new HashMap<>(8);
+ node.getAllInterfaces().stream()
+ .flatMap(i -> i.getMethods().stream())
+ .filter(MethodNode::isDefault)
+ .forEach(m -> {
+ if (!m.isDefault()) return;
+
+ String signature = methodDescriptorWithoutReturnType(m);
+ MethodNode existing = defaultMethods.get(signature);
+ if (existing != null) {
+ throw new RuntimeParserException(
+ (node.isInterface() ? "interface" : "class") + " " + node.getName()
+ + " inherits unrelated defaults for " + m.getTypeDescriptor()
+ + " from types " + existing.getDeclaringClass().getName()
+ + " and " + m.getDeclaringClass().getName(), sourceOf(m));
+ }
+ defaultMethods.put(signature, 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..3f451e9bec
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy10381.groovy
@@ -0,0 +1,173 @@
+/*
+ * 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_extends() {
+ def err = shouldFail '''
+ public interface A {
+ 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 {
+ 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()
+ }
+ }
+}