Skip to content

Commit c226758

Browse files
Marcono1234smowton
authored andcommitted
Java: Add classes and predicates for @Repeatable
1 parent 02c8fe9 commit c226758

File tree

2 files changed

+60
-0
lines changed

2 files changed

+60
-0
lines changed

java/ql/lib/semmle/code/java/Annotation.qll

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ class Annotatable extends Element {
184184

185185
/**
186186
* Gets an annotation that applies to this element, including inherited annotations.
187+
* The results only include _direct_ annotations; _indirect_ annotations, that is
188+
* repeated annotations in an (implicit) container annotation, are not included.
187189
*/
188190
// This predicate is overridden by Class to consider inherited annotations
189191
cached
@@ -194,6 +196,42 @@ class Annotatable extends Element {
194196
*/
195197
Annotation getADeclaredAnnotation() { result.getAnnotatedElement() = this }
196198

199+
/** Gets an _indirect_ (= repeated) annotation. */
200+
// 'indirect' as defined by https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/AnnotatedElement.html
201+
private Annotation getAnIndirectAnnotation() {
202+
exists(AnnotationType t, Annotation containerAnn |
203+
t = result.getType() and
204+
containerAnn = getADeclaredAnnotation() and
205+
containerAnn.getType() = t.getContainingAnnotationType()
206+
|
207+
result = containerAnn.getAValue("value")
208+
)
209+
}
210+
211+
private Annotation getAnAssociatedAnnotation(AnnotationType t) {
212+
result.getType() = t and
213+
// Direct or indirect annotation
214+
if getADeclaredAnnotation().getType() = t or getAnIndirectAnnotation().getType() = t
215+
then (
216+
result = getADeclaredAnnotation() or result = getAnIndirectAnnotation()
217+
) else (
218+
// Only if neither a direct nor an indirect annotation is present look for an inherited one
219+
t.isInherited() and
220+
// @Inherited only works for classes; cast to Annotatable is necessary because predicate is private
221+
result = this.(Class).getASupertype().(Class).(Annotatable).getAnAssociatedAnnotation(t)
222+
)
223+
}
224+
225+
/**
226+
* Gets an annotation _associated_ with this element, that is:
227+
* - An annotation directly present on this element, or
228+
* - An annotation indirectly present on this element (in the form of a repeated annotation), or
229+
* - If an annotation of a type is neither directly nor indirectly present
230+
* the result is an associated inherited annotation (recursively)
231+
*/
232+
// 'associated' as defined by https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/AnnotatedElement.html
233+
Annotation getAnAssociatedAnnotation() { result = getAnAssociatedAnnotation(_) }
234+
197235
/**
198236
* Holds if this or any enclosing `Annotatable` has a `@SuppressWarnings("<category>")`
199237
* annotation attached to it for the specified `category`.
@@ -265,6 +303,17 @@ class AnnotationType extends Interface {
265303
// The compiler does not completely implement that, but pretend it did
266304
any()
267305
}
306+
307+
/** Holds if this annotation type is annotated with the meta-annotation `@Repeatable`. */
308+
predicate isRepeatable() { getADeclaredAnnotation() instanceof RepeatableAnnotation }
309+
310+
/**
311+
* If this annotation type is annotated with the meta-annotation `@Repeatable`,
312+
* gets the annotation type which acts as _containing annotation type_.
313+
*/
314+
AnnotationType getContainingAnnotationType() {
315+
result = getADeclaredAnnotation().(RepeatableAnnotation).getContainingType()
316+
}
268317
}
269318

270319
/** An annotation element is a member declared in an annotation type. */

java/ql/lib/semmle/code/java/JDKAnnotations.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,17 @@ class RetentionAnnotation extends Annotation {
9191
}
9292
}
9393

94+
/** A `@Repeatable` annotation. */
95+
class RepeatableAnnotation extends Annotation {
96+
RepeatableAnnotation() { getType().hasQualifiedName("java.lang.annotation", "Repeatable") }
97+
98+
/**
99+
* Gets the annotation type which acts as _containing type_, grouping multiple
100+
* repeatable annotations together.
101+
*/
102+
AnnotationType getContainingType() { result = getValueClass("value") }
103+
}
104+
94105
/**
95106
* An annotation suggesting that the annotated element may be accessed reflectively.
96107
*

0 commit comments

Comments
 (0)