Skip to content

Commit 70d1058

Browse files
committed
Java: Address Annotation review comments and add change note
1 parent f968217 commit 70d1058

File tree

6 files changed

+93
-52
lines changed

6 files changed

+93
-52
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
category: deprecated
3+
---
4+
* The predicate `Annotation.getAValue()` has been deprecated because it might lead to obtaining the value of the wrong annotation element by accident. `getValue(string)` (or one of the value type specific predicates) should be used to explicitly specify the name of the annotation element.
5+
* The predicate `Annotation.getAValue(string)` has been renamed to `getAnArrayValue(string)`.
6+
* The predicate `SuppressWarningsAnnotation.getASuppressedWarningLiteral()` has been deprecated because it unnecessarily restricts the result type; `getASuppressedWarning()` should be used instead.
7+
* The predicates `TargetAnnotation.getATargetExpression()` and `RetentionAnnotation.getRetentionPolicyExpression()` have been deprecated because getting the enum constant read expression is rarely useful, instead the corresponding predicates for getting the name of the referenced enum constants should be used.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
category: feature
3+
---
4+
* The predicates of the CodeQL class `Annotation` have been improved:
5+
* Convenience value type specific predicates have been added, such as `getEnumConstantValue(string)` or `getStringValue(string)`.
6+
* Convenience predicates for elements with array values have been added, such as `getAnEnumConstantArrayValue(string)`. While the behavior of the existing predicates has not changed, usage of them should be reviewed (or replaced with the newly added predicate) to make sure they work correctly for elements with array values.
7+
* Some internal CodeQL usage of the `Annotation` predicates has been adjusted and corrected; this might affect the results of some queries.
8+
* New predicates have been added to the CodeQL class `Annotatable` to support getting declared and associated annotations. As part of that, `hasAnnotation()` has been changed to also consider inherited annotations, to be consistent with `hasAnnotation(string, string)` and `getAnAnnotation()`. The newly added predicate `hasDeclaredAnnotation()` can be used as replacement for the old functionality.
9+
* New predicates have been added to the CodeQL class `AnnotationType` to simplify getting information about usage of JDK meta-annotations, such as `@Retention`.

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

Lines changed: 72 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class Annotation extends @annotation, Expr {
6161
/**
6262
* Gets the value of the annotation element with the specified `name`.
6363
* This includes default values in case no explicit value is specified.
64-
* For elements with an array value type this might have an `ArrayInit` as result.
64+
* For elements with an array value type this might get an `ArrayInit` instance.
6565
* To properly handle array values, prefer the predicate `getAnArrayValue`.
6666
*/
6767
Expr getValue(string name) { filteredAnnotValue(this, this.getAnnotationElement(name), result) }
@@ -73,7 +73,9 @@ class Annotation extends @annotation, Expr {
7373
*
7474
* If the element value type is an enum type array, use `getAnEnumConstantArrayValue`.
7575
*/
76-
EnumConstant getEnumConstantValue(string name) { result = getValue(name).(FieldRead).getField() }
76+
EnumConstant getEnumConstantValue(string name) {
77+
result = this.getValue(name).(FieldRead).getField()
78+
}
7779

7880
/**
7981
* If the value type of the annotation element with the specified `name` is `String`,
@@ -83,9 +85,9 @@ class Annotation extends @annotation, Expr {
8385
* If the element value type is a string array, use `getAStringArrayValue`.
8486
*/
8587
string getStringValue(string name) {
86-
// Uses CompileTimeConstantExpr instead of StringLiteral because value can
87-
// be read of final variable as well
88-
result = getValue(name).(CompileTimeConstantExpr).getStringValue()
88+
// Uses CompileTimeConstantExpr instead of StringLiteral because this can for example
89+
// be a read from a final variable as well.
90+
result = this.getValue(name).(CompileTimeConstantExpr).getStringValue()
8991
}
9092

9193
/**
@@ -96,9 +98,9 @@ class Annotation extends @annotation, Expr {
9698
* If the element value type is an `int` array, use `getAnIntArrayValue`.
9799
*/
98100
int getIntValue(string name) {
99-
// Uses CompileTimeConstantExpr instead of IntegerLiteral because value can
100-
// be read of final variable as well
101-
result = getValue(name).(CompileTimeConstantExpr).getIntValue()
101+
// Uses CompileTimeConstantExpr instead of IntegerLiteral because this can for example
102+
// be a read from a final variable as well.
103+
result = this.getValue(name).(CompileTimeConstantExpr).getIntValue()
102104
}
103105

104106
/**
@@ -107,19 +109,19 @@ class Annotation extends @annotation, Expr {
107109
* no explicit value is specified.
108110
*/
109111
boolean getBooleanValue(string name) {
110-
// Uses CompileTimeConstantExpr instead of BooleanLiteral because value can
111-
// be read of final variable as well
112-
result = getValue(name).(CompileTimeConstantExpr).getBooleanValue()
112+
// Uses CompileTimeConstantExpr instead of BooleanLiteral because this can for example
113+
// be a read from a final variable as well.
114+
result = this.getValue(name).(CompileTimeConstantExpr).getBooleanValue()
113115
}
114116

115117
/**
116-
* If the annotation element with the specified `name` has a Java `Class` as value type,
117-
* gets the referenced type used as value for that element. This includes default values
118-
* in case no explicit value is specified.
118+
* If the value type of the annotation element with the specified `name` is `java.lang.Class`,
119+
* gets the type referred to by that `Class`. This includes default values in case no explicit
120+
* value is specified.
119121
*
120122
* If the element value type is a `Class` array, use `getATypeArrayValue`.
121123
*/
122-
Type getTypeValue(string name) { result = getValue(name).(TypeLiteral).getReferencedType() }
124+
Type getTypeValue(string name) { result = this.getValue(name).(TypeLiteral).getReferencedType() }
123125

124126
/** Gets the element being annotated. */
125127
Element getTarget() { result = this.getAnnotatedElement() }
@@ -134,21 +136,21 @@ class Annotation extends @annotation, Expr {
134136
* type. This includes default values in case no explicit value is specified.
135137
*
136138
* If the annotation element is defined with an array initializer, then the result will be one of the
137-
* elements of that array. Otherwise, the result will be the single expression defined for the value.
139+
* elements of that array. Otherwise, the result will be the single expression used as value.
138140
*/
139-
Expr getAnArrayValue(string name) { result = getArrayValue(name, _) }
141+
Expr getAnArrayValue(string name) { result = this.getArrayValue(name, _) }
140142

141143
/**
142144
* DEPRECATED: Predicate has been renamed to `getAnArrayValue`
143145
*/
144-
deprecated Expr getAValue(string name) { result = getAnArrayValue(name) }
146+
deprecated Expr getAValue(string name) { result = this.getAnArrayValue(name) }
145147

146148
/**
147149
* Gets a value of the annotation element with the specified `name`, which must be declared as an enum
148150
* type array. This includes default values in case no explicit value is specified.
149151
*
150152
* If the annotation element is defined with an array initializer, then the result will be one of the
151-
* elements of that array. Otherwise, the result will be the single expression defined for the value.
153+
* elements of that array. Otherwise, the result will be the single expression used as value.
152154
*/
153155
EnumConstant getAnEnumConstantArrayValue(string name) {
154156
result = this.getAnArrayValue(name).(FieldRead).getField()
@@ -159,7 +161,7 @@ class Annotation extends @annotation, Expr {
159161
* array. This includes default values in case no explicit value is specified.
160162
*
161163
* If the annotation element is defined with an array initializer, then the result will be one of the
162-
* elements of that array. Otherwise, the result will be the single expression defined for the value.
164+
* elements of that array. Otherwise, the result will be the single expression used as value.
163165
*/
164166
string getAStringArrayValue(string name) {
165167
result = this.getAnArrayValue(name).(CompileTimeConstantExpr).getStringValue()
@@ -170,7 +172,7 @@ class Annotation extends @annotation, Expr {
170172
* array. This includes default values in case no explicit value is specified.
171173
*
172174
* If the annotation element is defined with an array initializer, then the result will be one of the
173-
* elements of that array. Otherwise, the result will be the single expression defined for the value.
175+
* elements of that array. Otherwise, the result will be the single expression used as value.
174176
*/
175177
int getAnIntArrayValue(string name) {
176178
result = this.getAnArrayValue(name).(CompileTimeConstantExpr).getIntValue()
@@ -181,7 +183,7 @@ class Annotation extends @annotation, Expr {
181183
* array. This includes default values in case no explicit value is specified.
182184
*
183185
* If the annotation element is defined with an array initializer, then the result will be one of the
184-
* elements of that array. Otherwise, the result will be the single expression defined for the value.
186+
* elements of that array. Otherwise, the result will be the single expression used as value.
185187
*/
186188
Type getATypeArrayValue(string name) {
187189
result = this.getAnArrayValue(name).(TypeLiteral).getReferencedType()
@@ -233,10 +235,10 @@ private predicate sourceAnnotValue(Annotation a, Method m, Expr val) {
233235
/** An abstract representation of language elements that can be annotated. */
234236
class Annotatable extends Element {
235237
/** Holds if this element has an annotation, including inherited annotations. */
236-
predicate hasAnnotation() { exists(getAnAnnotation()) }
238+
predicate hasAnnotation() { exists(this.getAnAnnotation()) }
237239

238240
/** Holds if this element has a declared annotation, excluding inherited annotations. */
239-
predicate hasDeclaredAnnotation() { exists(getADeclaredAnnotation()) }
241+
predicate hasDeclaredAnnotation() { exists(this.getADeclaredAnnotation()) }
240242

241243
/**
242244
* Holds if this element has the specified annotation, including inherited
@@ -253,21 +255,23 @@ class Annotatable extends Element {
253255
* The results only include _direct_ annotations; _indirect_ annotations, that is
254256
* repeated annotations in an (implicit) container annotation, are not included.
255257
*/
256-
// This predicate is overridden by Class to consider inherited annotations
257258
cached
258-
Annotation getAnAnnotation() { result = getADeclaredAnnotation() }
259+
Annotation getAnAnnotation() {
260+
// This predicate is overridden by Class to consider inherited annotations
261+
result = this.getADeclaredAnnotation()
262+
}
259263

260264
/**
261265
* Gets an annotation that is declared on this element, excluding inherited annotations.
262266
*/
263267
Annotation getADeclaredAnnotation() { result.getAnnotatedElement() = this }
264268

265269
/** Gets an _indirect_ (= repeated) annotation. */
266-
// 'indirect' as defined by https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/AnnotatedElement.html
267270
private Annotation getAnIndirectAnnotation() {
271+
// 'indirect' as defined by https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/AnnotatedElement.html
268272
exists(AnnotationType t, Annotation containerAnn |
269273
t = result.getType() and
270-
containerAnn = getADeclaredAnnotation() and
274+
containerAnn = this.getADeclaredAnnotation() and
271275
containerAnn.getType() = t.getContainingAnnotationType()
272276
|
273277
result = containerAnn.getAnArrayValue("value")
@@ -276,12 +280,14 @@ class Annotatable extends Element {
276280

277281
private Annotation getADeclaredAssociatedAnnotation(AnnotationType t) {
278282
// Direct or indirect annotation
279-
result.getType() = t and result = [getADeclaredAnnotation(), getAnIndirectAnnotation()]
283+
result.getType() = t and
284+
result = [this.getADeclaredAnnotation(), this.getAnIndirectAnnotation()]
280285
}
281286

282287
private Annotation getAnAssociatedAnnotation(AnnotationType t) {
283-
if exists(getADeclaredAssociatedAnnotation(t))
284-
then result = getADeclaredAssociatedAnnotation(t)
288+
// 'associated' as defined by https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/AnnotatedElement.html
289+
if exists(this.getADeclaredAssociatedAnnotation(t))
290+
then result = this.getADeclaredAssociatedAnnotation(t)
285291
else (
286292
// Only if neither a direct nor an indirect annotation is present look for an inherited one
287293
t.isInherited() and
@@ -297,8 +303,7 @@ class Annotatable extends Element {
297303
* - If an annotation of a type is neither directly nor indirectly present
298304
* the result is an associated inherited annotation (recursively)
299305
*/
300-
// 'associated' as defined by https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/AnnotatedElement.html
301-
Annotation getAnAssociatedAnnotation() { result = getAnAssociatedAnnotation(_) }
306+
Annotation getAnAssociatedAnnotation() { result = this.getAnAssociatedAnnotation(_) }
302307

303308
/**
304309
* Holds if this or any enclosing `Annotatable` has a `@SuppressWarnings("<category>")`
@@ -333,12 +338,12 @@ class AnnotationType extends Interface {
333338

334339
/** Holds if this annotation type is annotated with the meta-annotation `@Inherited`. */
335340
predicate isInherited() {
336-
getADeclaredAnnotation().getType().hasQualifiedName("java.lang.annotation", "Inherited")
341+
this.getADeclaredAnnotation().getType().hasQualifiedName("java.lang.annotation", "Inherited")
337342
}
338343

339344
/** Holds if this annotation type is annotated with the meta-annotation `@Documented`. */
340345
predicate isDocumented() {
341-
getADeclaredAnnotation().getType().hasQualifiedName("java.lang.annotation", "Documented")
346+
this.getADeclaredAnnotation().getType().hasQualifiedName("java.lang.annotation", "Documented")
342347
}
343348

344349
/**
@@ -347,8 +352,8 @@ class AnnotationType extends Interface {
347352
* policy is specified the result is `CLASS`.
348353
*/
349354
string getRetentionPolicy() {
350-
if getADeclaredAnnotation() instanceof RetentionAnnotation
351-
then result = getADeclaredAnnotation().(RetentionAnnotation).getRetentionPolicy()
355+
if this.getADeclaredAnnotation() instanceof RetentionAnnotation
356+
then result = this.getADeclaredAnnotation().(RetentionAnnotation).getRetentionPolicy()
352357
else
353358
// If not explicitly specified retention is CLASS
354359
result = "CLASS"
@@ -358,29 +363,49 @@ class AnnotationType extends Interface {
358363
* Holds if the element type is a possible target for this annotation type.
359364
* The `elementType` is the name of one of the `java.lang.annotation.ElementType`
360365
* enum constants. If no explicit target is specified for this annotation type
361-
* it is considered to be applicable to all elements.
366+
* it is considered to be applicable in all declaration contexts.
362367
*/
363-
// Note: Cannot use a predicate with string as result because annotation type without
364-
// explicit @Target can be applied to all targets, requiring to hardcode element types here
365368
bindingset[elementType]
366369
predicate isATargetType(string elementType) {
367-
if getADeclaredAnnotation() instanceof TargetAnnotation
368-
then elementType = getADeclaredAnnotation().(TargetAnnotation).getATargetElementType()
370+
/*
371+
* Note: Cannot use a predicate with string as result because annotation type without
372+
* explicit @Target can be applied in all declaration contexts, requiring to hardcode
373+
* element types here; then the results could become outdated if this predicate is not
374+
* updated for future JDK versions, or it could have irritating results, e.g. RECORD_COMPONENT
375+
* for a database created for Java 8.
376+
*
377+
* Could in theory read java.lang.annotation.ElementType constants from database, but might
378+
* be brittle in case ElementType is not present in the database for whatever reason.
379+
*/
380+
381+
if this.getADeclaredAnnotation() instanceof TargetAnnotation
382+
then elementType = this.getADeclaredAnnotation().(TargetAnnotation).getATargetElementType()
369383
else
370-
// No Target annotation means "applicable to all contexts" since JDK 14, see https://bugs.openjdk.java.net/browse/JDK-8231435
371-
// The compiler does not completely implement that, but pretend it did
372-
any()
384+
/*
385+
* Behavior for missing @Target annotation changed between Java versions. In older Java
386+
* versions it allowed usage in most (but not all) declaration contexts. Then for Java 14
387+
* JDK-8231435 changed it to allow usage in all declaration and type contexts. In Java 17
388+
* it was changed by JDK-8261610 to only allow usage in all declaration contexts, but not
389+
* in type contexts anymore. However, during these changes javac did not always comply with
390+
* the specification, see for example JDK-8254023.
391+
*
392+
* For simplicity pretend the latest behavior defined by the JLS applied in all versions;
393+
* that means any declaration context is allowed, but type contexts (represented by TYPE_USE,
394+
* see JLS 17 section 9.6.4.1) are not allowed.
395+
*/
396+
397+
elementType != "TYPE_USE"
373398
}
374399

375400
/** Holds if this annotation type is annotated with the meta-annotation `@Repeatable`. */
376-
predicate isRepeatable() { getADeclaredAnnotation() instanceof RepeatableAnnotation }
401+
predicate isRepeatable() { this.getADeclaredAnnotation() instanceof RepeatableAnnotation }
377402

378403
/**
379404
* If this annotation type is annotated with the meta-annotation `@Repeatable`,
380405
* gets the annotation type which acts as _containing annotation type_.
381406
*/
382407
AnnotationType getContainingAnnotationType() {
383-
result = getADeclaredAnnotation().(RepeatableAnnotation).getContainingType()
408+
result = this.getADeclaredAnnotation().(RepeatableAnnotation).getContainingType()
384409
}
385410
}
386411

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,13 @@ class RetentionAnnotation extends Annotation {
8080

8181
/** A `@Repeatable` annotation. */
8282
class RepeatableAnnotation extends Annotation {
83-
RepeatableAnnotation() { getType().hasQualifiedName("java.lang.annotation", "Repeatable") }
83+
RepeatableAnnotation() { this.getType().hasQualifiedName("java.lang.annotation", "Repeatable") }
8484

8585
/**
8686
* Gets the annotation type which acts as _containing type_, grouping multiple
8787
* repeatable annotations together.
8888
*/
89-
AnnotationType getContainingType() { result = getTypeValue("value") }
89+
AnnotationType getContainingType() { result = this.getTypeValue("value") }
9090
}
9191

9292
/**

java/ql/lib/semmle/code/java/frameworks/spring/SpringAutowire.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ class SpringQualifierAnnotation extends Annotation {
323323
/**
324324
* Gets the value of the qualifier field for this qualifier.
325325
*/
326-
string getQualifierValue() { result = getStringValue("value") }
326+
string getQualifierValue() { result = this.getStringValue("value") }
327327

328328
/**
329329
* Gets the bean definition in an XML file that this qualifier resolves to, if any.
@@ -346,7 +346,7 @@ class SpringResourceAnnotation extends Annotation {
346346
/**
347347
* Gets the specified name value, if any.
348348
*/
349-
string getNameValue() { result = getStringValue("name") }
349+
string getNameValue() { result = this.getStringValue("name") }
350350

351351
/**
352352
* Gets the bean definition in an XML file that the resource resolves to, if any.

java/ql/lib/semmle/code/java/frameworks/spring/SpringComponentScan.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ class SpringComponent extends RefType {
138138
if exists(this.getComponentAnnotation().getValue("value"))
139139
then
140140
// If the name has been specified in the component annotation, use that.
141-
result = getComponentAnnotation().getStringValue("value")
141+
result = this.getComponentAnnotation().getStringValue("value")
142142
else
143143
// Otherwise use the name of the class, with the initial letter lower cased.
144144
exists(string name | name = this.getName() |

0 commit comments

Comments
 (0)