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/11/28 19:45:59 UTC

[groovy] branch master updated: GROOVY-10857: `hasMetaAnnotations` cycle check

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 3e13b0aee5 GROOVY-10857: `hasMetaAnnotations` cycle check
3e13b0aee5 is described below

commit 3e13b0aee5f7963d835a4a2f9a10b54f2cdbe193
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Nov 28 13:08:26 2022 -0600

    GROOVY-10857: `hasMetaAnnotations` cycle check
---
 src/test/gls/annotations/AnnotationTest.groovy     | 19 +++++++
 .../groovy/contracts/util/AnnotationUtils.java     | 30 ++++++----
 .../contracts/util/AnnotationUtilsTests.groovy     | 66 +++++++++++++++-------
 3 files changed, 85 insertions(+), 30 deletions(-)

diff --git a/src/test/gls/annotations/AnnotationTest.groovy b/src/test/gls/annotations/AnnotationTest.groovy
index ed5adac997..d96b1970ab 100644
--- a/src/test/gls/annotations/AnnotationTest.groovy
+++ b/src/test/gls/annotations/AnnotationTest.groovy
@@ -1128,4 +1128,23 @@ final class AnnotationTest {
             }
         '''
     }
+
+    @Test // GROOVY-10857
+    void testAnnotationWithCircularReference() {
+        assertScript shell, '''
+            @A @Documented @Retention(RUNTIME) @Target(TYPE)
+            @interface A {
+            }
+            @A @Documented @Retention(RUNTIME) @Target([FIELD,METHOD,PARAMETER])
+            @interface B {
+            }
+            interface I<T> {
+                @B T m()
+            }
+            def obj = new I<Object>() {
+                def m() { return 0 }
+            }
+            assert obj.m() == 0
+        '''
+    }
 }
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/AnnotationUtils.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/AnnotationUtils.java
index 1cd89eb7b8..ef44b7cc81 100644
--- a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/AnnotationUtils.java
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/AnnotationUtils.java
@@ -27,6 +27,7 @@ import org.codehaus.groovy.ast.MethodNode;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * <p>Helper methods for reading/getting {@link org.codehaus.groovy.ast.AnnotationNode} instances.</p>
@@ -113,20 +114,29 @@ public class AnnotationUtils {
      * @return a list of {@link AnnotationNode} instances which implement the given <tt>metaAnnotationClass</tt>
      */
     public static List<AnnotationNode> hasMetaAnnotations(AnnotatedNode annotatedNode, String metaAnnotationClassName) {
-
-        ArrayList<AnnotationNode> result = new ArrayList<AnnotationNode>();
+        List<AnnotationNode> result = new ArrayList<>();
+        Set<ClassNode> seen = new java.util.HashSet<>();
+        ClassNode type = ClassHelper.makeWithoutCaching(metaAnnotationClassName);
 
         for (AnnotationNode annotationNode : annotatedNode.getAnnotations()) {
-            if (CandidateChecks.isRuntimeClass(annotationNode.getClassNode())) continue;
+            if (hasMetaAnnotation(annotationNode.getClassNode(), type, seen))
+                result.add(annotationNode);
+        }
 
-            // is the annotation marked with the given meta annotation?
-            List<AnnotationNode> metaAnnotations = annotationNode.getClassNode().getAnnotations(ClassHelper.makeWithoutCaching(metaAnnotationClassName));
-            if (metaAnnotations.isEmpty()) {
-                metaAnnotations = hasMetaAnnotations(annotationNode.getClassNode(), metaAnnotationClassName);
-            }
+        return result;
+    }
 
-            if (metaAnnotations.size() > 0) result.add(annotationNode);
+    private static boolean hasMetaAnnotation(ClassNode annotationType, ClassNode metaAnnotationType, Set<ClassNode> cycleCheck) {
+        if (!CandidateChecks.isRuntimeClass(annotationType) && cycleCheck.add(annotationType)) {
+            if (!annotationType.getAnnotations(metaAnnotationType).isEmpty()) {
+                return true;
+            }
+            for (AnnotationNode annotationNode : annotationType.getAnnotations()) {
+                if (hasMetaAnnotation(annotationNode.getClassNode(), metaAnnotationType, cycleCheck)) {
+                    return true;
+                }
+            }
         }
-        return result;
+        return false;
     }
 }
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/util/AnnotationUtilsTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/util/AnnotationUtilsTests.groovy
index 7adcca937a..16adb8b538 100644
--- a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/util/AnnotationUtilsTests.groovy
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/util/AnnotationUtilsTests.groovy
@@ -18,41 +18,67 @@
  */
 package org.apache.groovy.contracts.util
 
+import org.apache.groovy.contracts.annotations.meta.Precondition
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.codehaus.groovy.ast.AnnotationNode
 import org.codehaus.groovy.ast.ClassHelper
 import org.codehaus.groovy.ast.ClassNode
 import org.codehaus.groovy.ast.MethodNode
 import org.codehaus.groovy.ast.Parameter
 import org.codehaus.groovy.ast.builder.AstBuilder
 import org.codehaus.groovy.control.CompilePhase
-import org.apache.groovy.contracts.annotations.meta.Precondition
-import org.apache.groovy.contracts.tests.basic.BaseTestClass
 import org.junit.Test
-import static org.junit.Assert.assertEquals
 
-class AnnotationUtilsTests extends BaseTestClass {
+final class AnnotationUtilsTests extends BaseTestClass {
+
+    @Test
+    void hasMetaAnnotations1() {
+        def astNodes = new AstBuilder().buildFromString(CompilePhase.SEMANTIC_ANALYSIS, false, '''
+            @Contracted
+            package tests
+
+            import groovy.contracts.*
+
+            class Tester {
+                @Requires({ param != null })
+                def method(def param) {
+                }
+            }
+        ''')
 
-    def source = '''
-    @Contracted
-    package tests
+        ClassNode classNode = astNodes[1]
+        MethodNode methodNode = classNode.getMethod('method', new Parameter(ClassHelper.OBJECT_TYPE, 'param'))
+        List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(methodNode, Precondition.getName())
 
-    import groovy.contracts.*
+        assert annotationNodes.size() == 1
+    }
 
-    class Tester {
+    @Test // GROOVY-10857
+    void hasMetaAnnotations2() {
+        def astNodes = new AstBuilder().buildFromString(CompilePhase.SEMANTIC_ANALYSIS, false, '''
+            import java.lang.annotation.*
+            import static java.lang.annotation.ElementType.*
+            import static java.lang.annotation.RetentionPolicy.*
 
-        @Requires({ param != null })
-        def method(def param) {}
+            @A @Documented @Retention(RUNTIME) @Target(TYPE)
+            @interface A {
+            }
+            @A @Documented @Retention(RUNTIME) @Target([FIELD,METHOD,PARAMETER])
+            @interface B {
+            }
+            interface I<T> {
+                @B T m()
+            }
+        ''')
 
-    }'''
+        ClassNode classNode = astNodes[3] // interface I
+        MethodNode methodNode = classNode.getMethod('m')
+        List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(methodNode, 'A')
 
-    @Test
-    void find_annotations_with_meta_annos() {
-        AstBuilder astBuilder = new AstBuilder()
-        def astNodes = astBuilder.buildFromString(CompilePhase.SEMANTIC_ANALYSIS, false, source)
+        assert annotationNodes.size() == 1
 
-        ClassNode classNode = astNodes[1]
-        MethodNode methodNode = classNode.getMethod("method", [new Parameter(ClassHelper.makeWithoutCaching("java.lang.Object"), "param")] as Parameter[])
+        annotationNodes = AnnotationUtils.hasMetaAnnotations(methodNode, Precondition.getName())
 
-        def annotationNodes = AnnotationUtils.hasMetaAnnotations(methodNode, Precondition.class.getName())
-        assertEquals(1, annotationNodes.size())
+        assert annotationNodes.isEmpty()
     }
 }