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()
}
}