-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathAnnotation.qll
More file actions
446 lines (395 loc) · 18.8 KB
/
Annotation.qll
File metadata and controls
446 lines (395 loc) · 18.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
/**
* Provides classes and predicates for working with Java annotations.
*
* Annotations are used to add meta-information to language elements in a
* uniform fashion. They can be seen as typed modifiers that can take
* parameters.
*
* Each annotation type has zero or more annotation elements that contain a
* name and possibly a value.
*/
overlay[local?]
module;
import Element
import Expr
import Type
import Member
import JDKAnnotations
/** Any annotation used to annotate language elements with meta-information. */
class Annotation extends @annotation, Expr {
/** Holds if this annotation applies to a declaration. */
predicate isDeclAnnotation() { this instanceof DeclAnnotation }
/** Holds if this annotation applies to a type. */
predicate isTypeAnnotation() { this instanceof TypeAnnotation }
/** Gets the element being annotated. */
Element getAnnotatedElement() {
exists(Element e | e = this.getParent() |
if e.(Field).getCompilationUnit().fromSource()
then
exists(FieldDeclaration decl |
decl.getField(0) = e and
result = decl.getAField()
)
else result = e
)
}
/** Gets the annotation type declaration for this annotation. */
override AnnotationType getType() {
result = Expr.super.getType().(Interface).getSourceDeclaration()
}
/** Gets the annotation element with the specified `name`. */
AnnotationElement getAnnotationElement(string name) {
result = this.getType().getAnnotationElement(name)
}
/**
* Gets the value of the annotation element with the specified `name`.
* This includes default values in case no explicit value is specified.
* For elements with an array value type this might get an `ArrayInit` instance.
* To properly handle array values, prefer the predicate `getAnArrayValue`.
*/
Expr getValue(string name) { filteredAnnotValue(this, this.getAnnotationElement(name), result) }
/**
* Gets the value of the annotation element, if its type is not an array.
* This guarantees that for consistency even elements of type array with a
* single value have no result, to prevent accidental error-prone usage.
*/
private Expr getNonArrayValue(string name) {
result = this.getValue(name) and
not this.getAnnotationElement(name).getType() instanceof Array
}
/**
* If the value type of the annotation element with the specified `name` is an enum type,
* gets the enum constant used as value for that element. This includes default values in
* case no explicit value is specified.
*
* If the element value type is an enum type array, use `getAnEnumConstantArrayValue`.
*/
EnumConstant getEnumConstantValue(string name) {
result = this.getNonArrayValue(name).(FieldRead).getField()
}
/**
* If the value type of the annotation element with the specified `name` is `String`,
* gets the string value used for that element. This includes default values in case no
* explicit value is specified.
*
* If the element value type is a string array, use `getAStringArrayValue`.
*/
string getStringValue(string name) {
// Uses CompileTimeConstantExpr instead of StringLiteral because this can for example
// be a read from a final variable as well.
result = this.getNonArrayValue(name).(CompileTimeConstantExpr).getStringValue()
}
/**
* If the value type of the annotation element with the specified `name` is `int` or
* a smaller integral type or `char`, gets the int value used for that element.
* This includes default values in case no explicit value is specified.
*
* If the element value type is an `int` array or an array of a smaller integral
* type or `char`, use `getAnIntArrayValue`.
*/
int getIntValue(string name) {
// Uses CompileTimeConstantExpr instead of IntegerLiteral because this can for example
// be a read from a final variable as well.
result = this.getNonArrayValue(name).(CompileTimeConstantExpr).getIntValue() and
// Verify that type is integral; ignore floating point elements with IntegerLiteral as value
this.getAnnotationElement(name).getType().hasName(["byte", "short", "int", "char"])
}
/**
* If the value type of the annotation element with the specified `name` is `boolean`,
* gets the boolean value used for that element. This includes default values in case
* no explicit value is specified.
*/
boolean getBooleanValue(string name) {
// Uses CompileTimeConstantExpr instead of BooleanLiteral because this can for example
// be a read from a final variable as well.
result = this.getNonArrayValue(name).(CompileTimeConstantExpr).getBooleanValue()
}
/**
* If the value type of the annotation element with the specified `name` is `java.lang.Class`,
* gets the type referred to by that `Class`. This includes default values in case no explicit
* value is specified.
*
* If the element value type is a `Class` array, use `getATypeArrayValue`.
*/
Type getTypeValue(string name) {
result = this.getNonArrayValue(name).(TypeLiteral).getReferencedType()
}
/** Gets the element being annotated. */
Element getTarget() { result = this.getAnnotatedElement() }
override string toString() { result = this.getType().getName() }
/** This expression's Halstead ID (used to compute Halstead metrics). */
override string getHalsteadID() { result = "Annotation" }
/**
* Gets a value of the annotation element with the specified `name`, which must be declared as an array
* type. This includes default values in case no explicit value is specified.
*
* If the annotation element is defined with an array initializer, then the result will be one of the
* elements of that array. Otherwise, the result will be the single expression used as value.
*/
Expr getAnArrayValue(string name) { result = this.getArrayValue(name, _) }
/**
* Gets a value of the annotation element with the specified `name`, which must be declared as an enum
* type array. This includes default values in case no explicit value is specified.
*
* If the annotation element is defined with an array initializer, then the result will be one of the
* elements of that array. Otherwise, the result will be the single expression used as value.
*/
EnumConstant getAnEnumConstantArrayValue(string name) {
result = this.getAnArrayValue(name).(FieldRead).getField()
}
/**
* Gets a value of the annotation element with the specified `name`, which must be declared as a string
* array. This includes default values in case no explicit value is specified.
*
* If the annotation element is defined with an array initializer, then the result will be one of the
* elements of that array. Otherwise, the result will be the single expression used as value.
*/
string getAStringArrayValue(string name) {
result = this.getAnArrayValue(name).(CompileTimeConstantExpr).getStringValue()
}
/**
* Gets a value of the annotation element with the specified `name`, which must be declared as an `int`
* array or an array of a smaller integral type or `char`. This includes default values in case no
* explicit value is specified.
*
* If the annotation element is defined with an array initializer, then the result will be one of the
* elements of that array. Otherwise, the result will be the single expression used as value.
*/
int getAnIntArrayValue(string name) {
result = this.getAnArrayValue(name).(CompileTimeConstantExpr).getIntValue() and
// Verify that type is integral; ignore floating point elements with IntegerLiteral as value
this.getAnnotationElement(name).getType().hasName(["byte[]", "short[]", "int[]", "char[]"])
}
/**
* Gets a value of the annotation element with the specified `name`, which must be declared as a `Class`
* array. This includes default values in case no explicit value is specified.
*
* If the annotation element is defined with an array initializer, then the result will be one of the
* elements of that array. Otherwise, the result will be the single expression used as value.
*/
Type getATypeArrayValue(string name) {
result = this.getAnArrayValue(name).(TypeLiteral).getReferencedType()
}
/**
* Gets the value at a given index of the annotation element with the specified `name`, which must be
* declared as an array type. This includes default values in case no explicit value is specified.
*
* If the annotation element is defined with an array initializer, then the result will be the element
* at the given index of that array, starting at 0. Otherwise, the result will be the single expression
* defined for the value and the `index` will be 0.
*/
Expr getArrayValue(string name, int index) {
this.getType().getAnnotationElement(name).getType() instanceof Array and
exists(Expr value | value = this.getValue(name) |
if value instanceof ArrayInit
then
// TODO: Currently reports incorrect index values in some cases, see https://github.com/github/codeql/issues/8645
result = value.(ArrayInit).getInit(index)
else (
index = 0 and result = value
)
)
}
override string getAPrimaryQlClass() { result = "Annotation" }
}
/** An `Annotation` that applies to a declaration. */
class DeclAnnotation extends @declannotation, Annotation { }
/** An `Annotation` that applies to a type. */
class TypeAnnotation extends @typeannotation, Annotation { }
/**
* There may be duplicate entries in annotValue(...) - one entry for
* information populated from bytecode, and one for information populated
* from source. This removes the duplication.
*/
private predicate filteredAnnotValue(Annotation a, Method m, Expr val) {
annotValue(a, m, val) and
(sourceAnnotValue(a, m, val) or not sourceAnnotValue(a, m, _))
}
private predicate sourceAnnotValue(Annotation a, Method m, Expr val) {
annotValue(a, m, val) and
val.getFile().isSourceFile()
}
/** An abstract representation of language elements that can be annotated. */
class Annotatable extends Element {
/**
* Holds if this element has an annotation, including inherited annotations.
* The retention policy of the annotation type is not considered.
*/
predicate hasAnnotation() { exists(this.getAnAnnotation()) }
/**
* Holds if this element has a declared annotation, excluding inherited annotations.
* The retention policy of the annotation type is not considered.
*/
predicate hasDeclaredAnnotation() { exists(this.getADeclaredAnnotation()) }
/**
* Holds if this element has the specified annotation, including inherited
* annotations. The retention policy of the annotation type is not considered.
*/
predicate hasAnnotation(string package, string name) {
exists(AnnotationType at | at = this.getAnAnnotation().getType() |
at.getNestedName() = name and at.getPackage().getName() = package
)
}
/**
* Gets an annotation that applies to this element, including inherited annotations.
* The results only include _direct_ annotations; _indirect_ annotations, that is
* repeated annotations in an (implicit) container annotation, are not included.
* The retention policy of the annotation type is not considered.
*/
cached
Annotation getAnAnnotation() {
// This predicate is overridden by Class to consider inherited annotations
result = this.getADeclaredAnnotation()
}
/**
* Gets an annotation that is declared on this element, excluding inherited annotations.
* The retention policy of the annotation type is not considered.
*/
Annotation getADeclaredAnnotation() { result.getAnnotatedElement() = this }
/** Gets an _indirect_ (= repeated) annotation. */
private Annotation getAnIndirectAnnotation() {
// 'indirect' as defined by https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/AnnotatedElement.html
exists(AnnotationType t, Annotation containerAnn |
t = result.getType() and
containerAnn = this.getADeclaredAnnotation() and
containerAnn.getType() = t.getContainingAnnotationType()
|
result = containerAnn.getAnArrayValue("value")
)
}
private Annotation getADeclaredAssociatedAnnotation(AnnotationType t) {
// Direct or indirect annotation
result.getType() = t and
result = [this.getADeclaredAnnotation(), this.getAnIndirectAnnotation()]
}
private Annotation getAnAssociatedAnnotation(AnnotationType t) {
// 'associated' as defined by https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/AnnotatedElement.html
if exists(this.getADeclaredAssociatedAnnotation(t))
then result = this.getADeclaredAssociatedAnnotation(t)
else (
// Only if neither a direct nor an indirect annotation is present look for an inherited one
t.isInherited() and
// @Inherited only works for classes; cast to Annotatable is necessary because predicate is private
result = this.(Class).getASupertype().(Class).(Annotatable).getAnAssociatedAnnotation(t)
)
}
/**
* Gets an annotation _associated_ with this element, that is:
* - An annotation directly present on this element, or
* - An annotation indirectly present on this element (in the form of a repeated annotation), or
* - If an annotation of a type is neither directly nor indirectly present
* the result is an associated inherited annotation (recursively)
*
* The retention policy of the annotation type is not considered.
*/
Annotation getAnAssociatedAnnotation() { result = this.getAnAssociatedAnnotation(_) }
/**
* Holds if this or any enclosing `Annotatable` has a `@SuppressWarnings("<category>")`
* annotation attached to it for the specified `category`.
*/
predicate suppressesWarningsAbout(string category) {
category = this.getAnAnnotation().(SuppressWarningsAnnotation).getASuppressedWarning()
or
this.(Member).getDeclaringType().suppressesWarningsAbout(category)
or
this.(Expr).getEnclosingCallable().suppressesWarningsAbout(category)
or
this.(Stmt).getEnclosingCallable().suppressesWarningsAbout(category)
or
this.(NestedClass).getEnclosingType().suppressesWarningsAbout(category)
or
this.(LocalClassOrInterface)
.getLocalTypeDeclStmt()
.getEnclosingCallable()
.suppressesWarningsAbout(category)
or
this.(LocalVariableDecl).getCallable().suppressesWarningsAbout(category)
}
}
/** An annotation type is a special kind of interface type declaration. */
class AnnotationType extends Interface {
AnnotationType() { isAnnotType(this) }
/** Gets the annotation element with the specified `name`. */
AnnotationElement getAnnotationElement(string name) {
methods(result, _, _, _, this, _) and result.hasName(name)
}
/** Gets an annotation element that is a member of this annotation type. */
AnnotationElement getAnAnnotationElement() { methods(result, _, _, _, this, _) }
/** Holds if this annotation type is annotated with the meta-annotation `@Inherited`. */
predicate isInherited() {
this.getADeclaredAnnotation().getType().hasQualifiedName("java.lang.annotation", "Inherited")
}
/** Holds if this annotation type is annotated with the meta-annotation `@Documented`. */
predicate isDocumented() {
this.getADeclaredAnnotation().getType().hasQualifiedName("java.lang.annotation", "Documented")
}
/**
* Gets the retention policy of this annotation type, that is, the name of one of the
* enum constants of `java.lang.annotation.RetentionPolicy`. If this annotation type
* has no `@Retention` annotation, the result is `CLASS`.
*/
string getRetentionPolicy() {
if this.getADeclaredAnnotation() instanceof RetentionAnnotation
then result = this.getADeclaredAnnotation().(RetentionAnnotation).getRetentionPolicy()
else
// If not explicitly specified retention is CLASS
result = "CLASS"
}
/**
* Holds if the element type is a possible target for this annotation type.
* The `elementType` is the name of one of the `java.lang.annotation.ElementType`
* enum constants.
*
* If this annotation type has no `@Target` annotation, it is considered to be applicable
* in all declaration contexts. This matches the behavior of the latest Java versions
* but differs from the behavior of older Java versions. This predicate must only be
* called with names of `ElementType` enum constants; for other values it might hold
* erroneously.
*/
bindingset[elementType]
predicate isATargetType(string elementType) {
/*
* Note: Cannot use a predicate with string as result because annotation type without
* explicit @Target can be applied in all declaration contexts, requiring to hardcode
* element types here; then the results could become outdated if this predicate is not
* updated for future JDK versions, or it could have irritating results, e.g. RECORD_COMPONENT
* for a database created for Java 8.
*
* Could in theory read java.lang.annotation.ElementType constants from database, but might
* be brittle in case ElementType is not present in the database for whatever reason.
*/
if this.getADeclaredAnnotation() instanceof TargetAnnotation
then elementType = this.getADeclaredAnnotation().(TargetAnnotation).getATargetElementType()
else
/*
* Behavior for missing @Target annotation changed between Java versions. In older Java
* versions it allowed usage in most (but not all) declaration contexts. Then for Java 14
* JDK-8231435 changed it to allow usage in all declaration and type contexts. In Java 17
* it was changed by JDK-8261610 to only allow usage in all declaration contexts, but not
* in type contexts anymore. However, during these changes javac did not always comply with
* the specification, see for example JDK-8254023.
*
* For simplicity pretend the latest behavior defined by the JLS applied in all versions;
* that means any declaration context is allowed, but type contexts (represented by TYPE_USE,
* see JLS 17 section 9.6.4.1) are not allowed.
*/
elementType != "TYPE_USE"
}
/** Holds if this annotation type is annotated with the meta-annotation `@Repeatable`. */
predicate isRepeatable() { this.getADeclaredAnnotation() instanceof RepeatableAnnotation }
/**
* If this annotation type is annotated with the meta-annotation `@Repeatable`,
* gets the annotation type which acts as _containing annotation type_.
*/
AnnotationType getContainingAnnotationType() {
result = this.getADeclaredAnnotation().(RepeatableAnnotation).getContainingType()
}
}
/** An annotation element is a member declared in an annotation type. */
class AnnotationElement extends Member {
AnnotationElement() { isAnnotElem(this) }
/** Gets the type of this annotation element. */
Type getType() { methods(this, _, _, result, _, _) }
/** Gets the Kotlin type of this annotation element. */
KotlinType getKotlinType() { methodsKotlinType(this, result) }
}