You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/01/05 20:38:24 UTC

[groovy] branch master updated: GROOVY-5106: verify repeat interface has same type argumnet(s)

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

emilles 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 9e420c1  GROOVY-5106: verify repeat interface has same type argumnet(s)
9e420c1 is described below

commit 9e420c1a3a90c737891abff9d1e84b7493d18bd0
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Wed Jan 5 14:30:47 2022 -0600

    GROOVY-5106: verify repeat interface has same type argumnet(s)
---
 .../org/codehaus/groovy/classgen/Verifier.java     | 88 +++++++++++++++++-----
 src/test/groovy/InterfaceTest.groovy               | 42 ++++++++---
 2 files changed, 102 insertions(+), 28 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
index 12ce5d3..edb89de 100644
--- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
@@ -124,6 +124,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.addMethodGenerics;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpec;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.createGenericsSpec;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.parameterizeType;
 import static org.codehaus.groovy.ast.tools.PropertyNodeUtils.adjustPropertyModifiersForMethod;
 
 /**
@@ -221,8 +222,6 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
 
     @Override
     public void visitClass(final ClassNode node) {
-        boolean skipGroovify = !node.getAnnotations(ClassHelper.make(CompileStatic.class)).isEmpty() &&
-                !node.getAnnotations(ClassHelper.make(POJO.class)).isEmpty();
         this.classNode = node;
 
         if (node.isRecord()) {
@@ -231,8 +230,11 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
                 throw new RuntimeParserException("Record '" + classNode.getNameWithoutPackage() + "' must not be abstract", classNode);
             }
         }
-        if (Traits.isTrait(node) // maybe possible to have this true in joint compilation mode
-                || classNode.isInterface()) {
+
+        checkForDuplicateInterfaces(node);
+
+        if (classNode.isInterface()
+                || Traits.isTrait(node)) { // maybe possible to have this true in joint compilation mode
             //interfaces have no constructors, but this code expects one,
             //so create a dummy and don't add it to the class node
             ConstructorNode dummy = new ConstructorNode(0, null);
@@ -245,21 +247,12 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
             return;
         }
 
-        ClassNode[] classNodes = classNode.getInterfaces();
-        List<String> interfaces = new ArrayList<>();
-        for (ClassNode classNode : classNodes) {
-            interfaces.add(classNode.getName());
-        }
-        Set<String> interfaceSet = new HashSet<>(interfaces);
-        if (interfaceSet.size() != interfaces.size()) {
-            throw new RuntimeParserException("Duplicate interfaces in implements list: " + interfaces, classNode);
-        }
-
         addDefaultParameterMethods(node);
         addDefaultParameterConstructors(node);
 
         final String classInternalName = BytecodeHelper.getClassInternalName(node);
-
+        boolean skipGroovify = !node.getAnnotations(ClassHelper.make(POJO.class)).isEmpty()
+                    && !node.getAnnotations(ClassHelper.make(CompileStatic.class)).isEmpty();
         if (!skipGroovify) {
             addStaticMetaClassField(node, classInternalName);
 
@@ -287,11 +280,11 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
         checkFinalVariables(node);
     }
 
-    private static final List<String> invalidNames = Arrays.asList("clone", "finalize", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait");
+    private static final String[] INVALID_COMPONENTS = {"clone", "finalize", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait"};
 
     private void detectInvalidRecordComponentNames(ClassNode node) {
         for (FieldNode fn : node.getFields()) {
-            if (invalidNames.contains(fn.getName())) {
+            if (Arrays.binarySearch(INVALID_COMPONENTS, fn.getName()) >= 0) {
                 throw new RuntimeParserException("Illegal record component name '" + fn.getName() + "'", fn);
             }
         }
@@ -365,6 +358,67 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
         };
     }
 
+    private static void addAllInterfaces(final Set<ClassNode> result, final ClassNode source) {
+        for (ClassNode in : source.getInterfaces()) {
+            in = parameterizeType(source, in);
+            if (result.add(in))
+                addAllInterfaces(result, in);
+        }
+        ClassNode sc = source.redirect().getUnresolvedSuperClass(false);
+        if (sc != null && !isObjectType(sc)) {
+            addAllInterfaces(result, parameterizeType(source, sc));
+        }
+    }
+
+    private static Set<ClassNode> getAllInterfaces(final ClassNode cn) {
+        Set<ClassNode> result = new HashSet<>();
+        if (cn.isInterface()) result.add(cn);
+        addAllInterfaces(result, cn);
+        return result;
+    }
+
+    private static void checkForDuplicateInterfaces(final ClassNode cn) {
+        ClassNode[] interfaces = cn.getInterfaces();
+        int nInterfaces = interfaces.length;
+        if (nInterfaces == 0) return;
+
+        if (nInterfaces > 1) {
+            List<String> interfaceNames = new ArrayList<>(nInterfaces);
+            for (ClassNode in : interfaces) interfaceNames.add(in.getName());
+            if (interfaceNames.size() != new HashSet<>(interfaceNames).size()) {
+                throw new RuntimeParserException("Duplicate interfaces in implements list: " + interfaceNames, cn);
+            }
+        }
+
+        // GROOVY-5106: check for same interface with different type argument(s)
+        List< Set<ClassNode> > allInterfaces = new ArrayList<>(nInterfaces + 1);
+        for (ClassNode in : interfaces) allInterfaces.add(getAllInterfaces(in));
+        allInterfaces.add(getAllInterfaces(cn.getUnresolvedSuperClass()));
+        if (nInterfaces == 1 && allInterfaces.get(1).isEmpty())
+            return; // no peer interface(s) to verify
+
+        for (int i = 0; i < nInterfaces; i += 1) {
+            for (ClassNode in : allInterfaces.get(i)) {
+                if (in.redirect().getGenericsTypes() != null) {
+                    for (int j = i + 1; j < nInterfaces + 1; j += 1) {
+                        Set<ClassNode> set = allInterfaces.get(j);
+                        if (set.contains(in)) {
+                            for (ClassNode t : set) { // find match and check generics
+                                if (t.equals(in)) {
+                                    String one = in.toString(false), two = t.toString(false);
+                                    if (!one.equals(two))
+                                        throw new RuntimeParserException("The interface " + in.getNameWithoutPackage() +
+                                            " cannot be implemented more than once with different arguments: " + one + " and " + two, cn);
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     private static void checkForDuplicateMethods(final ClassNode cn) {
         Set<String> descriptors = new HashSet<>();
         for (MethodNode mn : cn.getMethods()) {
diff --git a/src/test/groovy/InterfaceTest.groovy b/src/test/groovy/InterfaceTest.groovy
index 0a1d27e..50afb49 100644
--- a/src/test/groovy/InterfaceTest.groovy
+++ b/src/test/groovy/InterfaceTest.groovy
@@ -20,20 +20,40 @@ package groovy
 
 import gls.CompilableTestSupport
 
-class InterfaceTest extends CompilableTestSupport {
+final class InterfaceTest extends CompilableTestSupport {
 
     void testGenericsInInterfaceMembers() {
         // control
-        shouldCompile """
-        interface I1 {
-            public <T> T copy1(T arg)
-            public <U extends CharSequence> U copy2(U arg)
-            public <V, W> V copy3(W arg)
-            public <N extends Number> void foo()
-        }
-        """
+        shouldCompile '''
+            interface I {
+                def <T>                      T m1(T x)
+                def <U extends CharSequence> U m2(U x)
+                def <V, W>                   V m3(W x)
+                def <N extends Number>    void m4(   )
+            }
+        '''
         // erroneous
-        shouldNotCompile "interface I2 { public <?> copy1(arg) }"
-        shouldNotCompile "interface I3 { public <? extends CharSequence> copy1(arg) }"
+        shouldNotCompile 'interface I { def <?> m(x) }'
+        shouldNotCompile 'interface I { def <? extends CharSequence> m(x) }'
+    }
+
+    // GROOVY-5106
+    void testReImplementsInterface1() {
+        def err = shouldFail '''
+            interface I<T> {}
+            interface J<T> extends I<T> {}
+            class X implements I<String>, J<Number> {}
+        '''
+        assert err.contains('The interface I cannot be implemented more than once with different arguments: I<java.lang.String> and I<java.lang.Number>')
+    }
+
+    // GROOVY-5106
+    void testReImplementsInterface2() {
+        def err = shouldFail '''
+            interface I<T> {}
+            class X implements I<Number> {}
+            class Y extends X implements I<String> {}
+        '''
+        assert err.contains('The interface I cannot be implemented more than once with different arguments: I<java.lang.String> and I<java.lang.Number>')
     }
 }