Skip to content

Commit f826342

Browse files
authored
Merge pull request #6246 from Marcono1234/marcono1234/annotation-improvements
Java: Improve and add predicates and classes for annotations
2 parents 91f9e89 + 14fa6d4 commit f826342

31 files changed

+1207
-110
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: 314 additions & 18 deletions
Large diffs are not rendered by default.

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ predicate depends(RefType t, RefType dep) {
7171
a.getAnnotatedElement().(Member).getDeclaringType() = t
7272
|
7373
usesType(a.getType(), dep) or
74-
usesType(a.getAValue().getType(), dep)
74+
usesType(a.getValue(_).getType(), dep) or
75+
usesType(a.getAnArrayValue(_).getType(), dep)
7576
)
7677
or
7778
// the type accessed in an `instanceof` expression in `t`.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ predicate numDepends(RefType t, RefType dep, int value) {
9090
|
9191
elem = a and usesType(a.getType(), dep)
9292
or
93-
elem = a.getAValue() and
93+
elem = [a.getValue(_), a.getAnArrayValue(_)] and
9494
elem.getFile().getExtension() = "java" and
9595
usesType(elem.(Expr).getType(), dep)
9696
)

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

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,76 +18,75 @@ class OverrideAnnotation extends Annotation {
1818
class SuppressWarningsAnnotation extends Annotation {
1919
SuppressWarningsAnnotation() { this.getType().hasQualifiedName("java.lang", "SuppressWarnings") }
2020

21-
/** Gets the `StringLiteral` of a warning suppressed by this annotation. */
22-
StringLiteral getASuppressedWarningLiteral() {
23-
result = this.getAValue() or
24-
result = this.getAValue().(ArrayInit).getAnInit()
25-
}
21+
/**
22+
* DEPRECATED: This predicate restricts the results to `StringLiteral`; prefer `getASuppressedWarning()`
23+
* to get the name of a suppressed warning.
24+
*
25+
* Gets the `StringLiteral` of a warning suppressed by this annotation.
26+
*/
27+
deprecated StringLiteral getASuppressedWarningLiteral() { result = this.getAnArrayValue("value") }
2628

2729
/** Gets the name of a warning suppressed by this annotation. */
28-
string getASuppressedWarning() { result = this.getASuppressedWarningLiteral().getValue() }
30+
string getASuppressedWarning() { result = this.getAStringArrayValue("value") }
2931
}
3032

3133
/** A `@Target` annotation. */
3234
class TargetAnnotation extends Annotation {
3335
TargetAnnotation() { this.getType().hasQualifiedName("java.lang.annotation", "Target") }
3436

3537
/**
38+
* DEPRECATED: Getting the field access expression is rarely useful. Use `getATargetElementType()`
39+
* to get the name of the target element.
40+
*
3641
* Gets a target expression within this annotation.
3742
*
3843
* For example, the field access `ElementType.FIELD` is a target expression in
3944
* `@Target({ElementType.FIELD, ElementType.METHOD})`.
4045
*/
41-
Expr getATargetExpression() {
42-
not result instanceof ArrayInit and
43-
(
44-
result = this.getAValue() or
45-
result = this.getAValue().(ArrayInit).getAnInit()
46-
)
47-
}
46+
deprecated Expr getATargetExpression() { result = this.getAnArrayValue("value") }
4847

4948
/**
5049
* Gets the name of a target element type.
5150
*
5251
* For example, `METHOD` is the name of a target element type in
5352
* `@Target({ElementType.FIELD, ElementType.METHOD})`.
5453
*/
55-
string getATargetElementType() {
56-
exists(EnumConstant ec |
57-
ec = this.getATargetExpression().(VarAccess).getVariable() and
58-
ec.getDeclaringType().hasQualifiedName("java.lang.annotation", "ElementType")
59-
|
60-
result = ec.getName()
61-
)
62-
}
54+
string getATargetElementType() { result = this.getAnEnumConstantArrayValue("value").getName() }
6355
}
6456

6557
/** A `@Retention` annotation. */
6658
class RetentionAnnotation extends Annotation {
6759
RetentionAnnotation() { this.getType().hasQualifiedName("java.lang.annotation", "Retention") }
6860

6961
/**
62+
* DEPRECATED: Getting the field access expression is rarely useful. Use `getRetentionPolicy()`
63+
* to get the name of the retention policy.
64+
*
7065
* Gets the retention policy expression within this annotation.
7166
*
7267
* For example, the field access `RetentionPolicy.RUNTIME` is the
7368
* retention policy expression in `@Retention(RetentionPolicy.RUNTIME)`.
7469
*/
75-
Expr getRetentionPolicyExpression() { result = this.getValue("value") }
70+
deprecated Expr getRetentionPolicyExpression() { result = this.getValue("value") }
7671

7772
/**
7873
* Gets the name of the retention policy of this annotation.
7974
*
8075
* For example, `RUNTIME` is the name of the retention policy
8176
* in `@Retention(RetentionPolicy.RUNTIME)`.
8277
*/
83-
string getRetentionPolicy() {
84-
exists(EnumConstant ec |
85-
ec = this.getRetentionPolicyExpression().(VarAccess).getVariable() and
86-
ec.getDeclaringType().hasQualifiedName("java.lang.annotation", "RetentionPolicy")
87-
|
88-
result = ec.getName()
89-
)
90-
}
78+
string getRetentionPolicy() { result = this.getEnumConstantValue("value").getName() }
79+
}
80+
81+
/** A `@Repeatable` annotation. */
82+
class RepeatableAnnotation extends Annotation {
83+
RepeatableAnnotation() { this.getType().hasQualifiedName("java.lang.annotation", "Repeatable") }
84+
85+
/**
86+
* Gets the annotation type which acts as _containing type_, grouping multiple
87+
* repeatable annotations together.
88+
*/
89+
AnnotationType getContainingType() { result = this.getTypeValue("value") }
9190
}
9291

9392
/**
@@ -119,11 +118,7 @@ abstract class NonReflectiveAnnotation extends Annotation { }
119118

120119
library class StandardNonReflectiveAnnotation extends NonReflectiveAnnotation {
121120
StandardNonReflectiveAnnotation() {
122-
exists(AnnotationType anntp | anntp = this.getType() |
123-
anntp.hasQualifiedName("java.lang", "Override") or
124-
anntp.hasQualifiedName("java.lang", "Deprecated") or
125-
anntp.hasQualifiedName("java.lang", "SuppressWarnings") or
126-
anntp.hasQualifiedName("java.lang", "SafeVarargs")
127-
)
121+
this.getType()
122+
.hasQualifiedName("java.lang", ["Override", "Deprecated", "SuppressWarnings", "SafeVarargs"])
128123
}
129124
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ private newtype TPrintAstNode =
120120
shouldPrint(lvde, _) and lvde.getParent() instanceof SingleLocalVarDeclParent
121121
} or
122122
TAnnotationsNode(Annotatable ann) {
123-
shouldPrint(ann, _) and ann.hasAnnotation() and not partOfAnnotation(ann)
123+
shouldPrint(ann, _) and ann.hasDeclaredAnnotation() and not partOfAnnotation(ann)
124124
} or
125125
TParametersNode(Callable c) { shouldPrint(c, _) and not c.hasNoParameters() } or
126126
TBaseTypesNode(ClassOrInterface ty) { shouldPrint(ty, _) } or

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,13 @@ class TestNGTestMethod extends Method {
161161
exists(TestNGTestAnnotation testAnnotation |
162162
testAnnotation = this.getAnAnnotation() and
163163
// The data provider must have the same name as the referenced data provider
164-
result.getDataProviderName() =
165-
testAnnotation.getValue("dataProvider").(StringLiteral).getValue()
164+
result.getDataProviderName() = testAnnotation.getStringValue("dataProvider")
166165
|
167166
// Either the data provider should be on the current class, or a supertype
168167
this.getDeclaringType().getAnAncestor() = result.getDeclaringType()
169168
or
170169
// Or the data provider class should be declared
171-
result.getDeclaringType() =
172-
testAnnotation.getValue("dataProviderClass").(TypeLiteral).getReferencedType()
170+
result.getDeclaringType() = testAnnotation.getTypeValue("dataProviderClass")
173171
)
174172
}
175173
}
@@ -227,9 +225,7 @@ class TestNGListenersAnnotation extends TestNGAnnotation {
227225
/**
228226
* Gets a listener defined in this annotation.
229227
*/
230-
TestNGListenerImpl getAListener() {
231-
result = this.getAValue("value").(TypeLiteral).getReferencedType()
232-
}
228+
TestNGListenerImpl getAListener() { result = this.getATypeArrayValue("value") }
233229
}
234230

235231
/**

java/ql/lib/semmle/code/java/frameworks/JAXB.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class JaxbType extends Class {
6060
this.getAnAnnotation() = a and
6161
a.getType().(JaxbAnnotationType).hasName("XmlAccessorType")
6262
|
63-
result.getAnAccess() = a.getValue("value")
63+
result = a.getEnumConstantValue("value")
6464
)
6565
}
6666

java/ql/lib/semmle/code/java/frameworks/JUnitAnnotations.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,5 @@ class RunWithAnnotation extends Annotation {
6464
/**
6565
* Gets the runner that will be used.
6666
*/
67-
Type getRunner() { result = this.getValue("value").(TypeLiteral).getReferencedType() }
67+
Type getRunner() { result = this.getTypeValue("value") }
6868
}

java/ql/lib/semmle/code/java/frameworks/JaxWS.qll

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -296,11 +296,7 @@ class JaxRSProducesAnnotation extends JaxRSAnnotation {
296296
/**
297297
* Gets a declared content type that can be produced by this resource.
298298
*/
299-
Expr getADeclaredContentTypeExpr() {
300-
result = this.getAValue() and not result instanceof ArrayInit
301-
or
302-
result = this.getAValue().(ArrayInit).getAnInit()
303-
}
299+
Expr getADeclaredContentTypeExpr() { result = this.getAnArrayValue("value") }
304300
}
305301

306302
/** An `@Consumes` annotation that describes content types can be consumed by this resource. */

java/ql/lib/semmle/code/java/frameworks/MyBatis.qll

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,7 @@ class IbatisSqlOperationAnnotation extends Annotation {
8585
/**
8686
* Gets this annotation's SQL statement string.
8787
*/
88-
string getSqlValue() {
89-
result = this.getAValue("value").(CompileTimeConstantExpr).getStringValue()
90-
}
88+
string getSqlValue() { result = this.getAStringArrayValue("value") }
9189
}
9290

9391
/**

java/ql/lib/semmle/code/java/frameworks/javaee/Persistence.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ class PersistentEntity extends RefType {
3333
}
3434

3535
/**
36-
* Gets the access type for this entity as defined by a `@javax.persistence.Access` annotation, if any.
36+
* Gets the access type for this entity as defined by a `@javax.persistence.Access` annotation,
37+
* if any, in lower case.
3738
*/
3839
string getAccessTypeFromAnnotation() {
3940
exists(AccessAnnotation accessType | accessType = this.getAnAnnotation() |
40-
result =
41-
accessType.getValue("value").(FieldRead).getField().(EnumConstant).getName().toLowerCase()
41+
result = accessType.getEnumConstantValue("value").getName().toLowerCase()
4242
)
4343
}
4444
}

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

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -311,9 +311,7 @@ class SpringQualifierDefinitionAnnotation extends Annotation {
311311
/**
312312
* Gets the value of the qualifier field for this qualifier.
313313
*/
314-
string getQualifierValue() {
315-
result = this.getValue("value").(CompileTimeConstantExpr).getStringValue()
316-
}
314+
string getQualifierValue() { result = this.getStringValue("value") }
317315
}
318316

319317
/**
@@ -325,9 +323,7 @@ class SpringQualifierAnnotation extends Annotation {
325323
/**
326324
* Gets the value of the qualifier field for this qualifier.
327325
*/
328-
string getQualifierValue() {
329-
result = this.getValue("value").(CompileTimeConstantExpr).getStringValue()
330-
}
326+
string getQualifierValue() { result = this.getStringValue("value") }
331327

332328
/**
333329
* Gets the bean definition in an XML file that this qualifier resolves to, if any.
@@ -350,9 +346,7 @@ class SpringResourceAnnotation extends Annotation {
350346
/**
351347
* Gets the specified name value, if any.
352348
*/
353-
string getNameValue() {
354-
result = this.getValue("name").(CompileTimeConstantExpr).getStringValue()
355-
}
349+
string getNameValue() { result = this.getStringValue("name") }
356350

357351
/**
358352
* 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: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,10 @@ class SpringComponentScan extends Annotation {
4040
*/
4141
string getBasePackages() {
4242
// "value" and "basePackages" are synonymous, and are simple strings
43-
result = this.getAValue("basePackages").(StringLiteral).getValue()
43+
result = this.getAStringArrayValue(["basePackages", "value"])
4444
or
45-
result = this.getAValue("value").(StringLiteral).getValue()
46-
or
47-
exists(TypeLiteral typeLiteral |
48-
// Base package classes are type literals whose package should be considered a base package.
49-
typeLiteral = this.getAValue("basePackageClasses")
50-
|
51-
result = typeLiteral.getReferencedType().(RefType).getPackage().getName()
52-
)
45+
// Base package classes are type literals whose package should be considered a base package.
46+
result = this.getATypeArrayValue("basePackageClasses").(RefType).getPackage().getName()
5347
}
5448
}
5549

@@ -144,8 +138,7 @@ class SpringComponent extends RefType {
144138
if exists(this.getComponentAnnotation().getValue("value"))
145139
then
146140
// If the name has been specified in the component annotation, use that.
147-
result =
148-
this.getComponentAnnotation().getValue("value").(CompileTimeConstantExpr).getStringValue()
141+
result = this.getComponentAnnotation().getStringValue("value")
149142
else
150143
// Otherwise use the name of the class, with the initial letter lower cased.
151144
exists(string name | name = this.getName() |
@@ -204,7 +197,7 @@ class SpringComponent extends RefType {
204197
.getType()
205198
.hasQualifiedName("org.springframework.context.annotation", "Profile")
206199
|
207-
result = profileAnnotation.getAValue("value").(StringLiteral).getValue()
200+
result = profileAnnotation.getAStringArrayValue("value")
208201
)
209202
}
210203
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,7 @@ class SpringRequestMappingMethod extends SpringControllerMethod {
154154
}
155155

156156
/** Gets the "value" @RequestMapping annotation value, if present. */
157-
string getValue() {
158-
result = requestMappingAnnotation.getValue("value").(CompileTimeConstantExpr).getStringValue()
159-
}
157+
string getValue() { result = requestMappingAnnotation.getStringValue("value") }
160158

161159
/** Holds if this is considered an `@ResponseBody` method. */
162160
predicate isResponseBody() {

java/ql/lib/semmle/code/java/frameworks/struts/StrutsAnnotations.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,5 @@ class StrutsActionsAnnotation extends StrutsAnnotation {
3434
/**
3535
* Gets an Action annotation contained in this Actions annotation.
3636
*/
37-
StrutsActionAnnotation getAnAction() { result = this.getAValue("value") }
37+
StrutsActionAnnotation getAnAction() { result = this.getAnArrayValue("value") }
3838
}

java/ql/src/AlertSuppressionAnnotations.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class SuppressionAnnotation extends SuppressWarningsAnnotation {
2323
string text;
2424

2525
SuppressionAnnotation() {
26-
text = this.getASuppressedWarningLiteral().getValue() and
26+
text = this.getASuppressedWarning() and
2727
exists(getAnnotationText(text))
2828
}
2929

java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.ql

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,5 @@ where
1919
m.getNumberOfParameters() = 1 and
2020
c.getArgument(0).getType() = p and
2121
p.getATypeArgument() = t and
22-
not exists(RetentionAnnotation a |
23-
t.getAnAnnotation() = a and
24-
a.getAValue().(VarAccess).getVariable().hasName("RUNTIME")
25-
)
22+
t.getRetentionPolicy() != "RUNTIME"
2623
select c, "Call to isAnnotationPresent where no annotation has the RUNTIME retention policy."

java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ class SpringControllerRequestMappingGetMethod extends SpringControllerGetMethod
4747
.getType()
4848
.hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and
4949
(
50-
this.getAnAnnotation().getValue("method").(VarAccess).getVariable().getName() = "GET" or
51-
this.getAnAnnotation().getValue("method").(ArrayInit).getSize() = 0 //Java code example: @RequestMapping(value = "test")
50+
this.getAnAnnotation().getAnEnumConstantArrayValue("method").getName() = "GET" or
51+
not exists(this.getAnAnnotation().getAnArrayValue("method")) //Java code example: @RequestMapping(value = "test")
5252
) and
5353
not this.getAParamType().getName() = "MultipartFile"
5454
}

java/ql/test/library-tests/annotation-arrays/test.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ from Field f, Annotation ann, Expr value, Expr valueChild
44
where
55
f.getDeclaringType().fromSource() and
66
ann = f.getAnAnnotation() and
7-
value = ann.getAValue() and
7+
value = ann.getValue(_) and
88
valueChild.getParent() = value
99
select f, ann, value, valueChild

0 commit comments

Comments
 (0)