Skip to content

Java: Improve and add predicates and classes for annotations #6246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
536f5c7
Java: Add `Annotation` value convenience predicates
Marcono1234 Jul 10, 2021
afb7462
Java: Clarify that `Annotation` value predicates have default value a…
Marcono1234 Jul 10, 2021
f69b6ee
Java: Clarify that `Annotatable` predicates consider inherited annota…
Marcono1234 Jul 10, 2021
02c8fe9
Java: Add convenience predicates for `AnnotationType`
Marcono1234 Jul 10, 2021
c226758
Java: Add classes and predicates for `@Repeatable`
Marcono1234 Jul 10, 2021
b96061a
Java: Rename Annotation value predicates
Marcono1234 Mar 27, 2022
fd5fdd8
Java: Rename Annotation.getAValue predicates for array values
Marcono1234 Mar 27, 2022
47e3895
Java: Improve Annotation.getAnAssociatedAnnotation
Marcono1234 Mar 27, 2022
998aa95
Java: Add convenience array value Annotation predicates
Marcono1234 Mar 27, 2022
e3c1b96
Java: Fix incorrect annotation handling for SpringControllerRequestMa…
Marcono1234 Mar 27, 2022
4ef2d15
Java: Deprecate error-prone and rarely used annotation predicates
Marcono1234 Mar 27, 2022
90a9364
Java: Rename Annotation.getAnArrayValue with index
Marcono1234 Mar 27, 2022
659a3a7
Java: Deprecate `RetentionAnnotation.getRetentionPolicyExpression()`
Marcono1234 Apr 1, 2022
8c9bdeb
Java: Address Annotation review comments and add change note
Marcono1234 Apr 1, 2022
37b1891
Java: Add annotation tests
Marcono1234 Apr 2, 2022
c8b9229
Java: Extend AnnotationType.isATargetType documentation
Marcono1234 Apr 14, 2022
1945f18
Apply suggestions from code review
aschackmull Apr 25, 2022
0ab5d46
Update test expectations now that the Java extractor's nested annotat…
smowton Sep 16, 2022
14fa6d4
Avoid deprecated Annotation.getAValue
smowton Sep 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
category: deprecated
---
* 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.
* The predicate `Annotation.getAValue(string)` has been renamed to `getAnArrayValue(string)`.
* The predicate `SuppressWarningsAnnotation.getASuppressedWarningLiteral()` has been deprecated because it unnecessarily restricts the result type; `getASuppressedWarning()` should be used instead.
* 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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, maybe there are use cases where one wants to get the location of the expression, e.g. to include it in the query result set. But those use cases might still be rare and rather apply to custom annotations and not these JDK default ones.

9 changes: 9 additions & 0 deletions java/ql/lib/change-notes/2022-04-01-annotation-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
category: feature
---
* The predicates of the CodeQL class `Annotation` have been improved:
* Convenience value type specific predicates have been added, such as `getEnumConstantValue(string)` or `getStringValue(string)`.
* 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.
* Some internal CodeQL usage of the `Annotation` predicates has been adjusted and corrected; this might affect the results of some queries.
* 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.
* New predicates have been added to the CodeQL class `AnnotationType` to simplify getting information about usage of JDK meta-annotations, such as `@Retention`.
332 changes: 314 additions & 18 deletions java/ql/lib/semmle/code/java/Annotation.qll

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion java/ql/lib/semmle/code/java/Dependency.qll
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ predicate depends(RefType t, RefType dep) {
a.getAnnotatedElement().(Member).getDeclaringType() = t
|
usesType(a.getType(), dep) or
usesType(a.getAValue().getType(), dep)
usesType(a.getValue(_).getType(), dep) or
usesType(a.getAnArrayValue(_).getType(), dep)
)
or
// the type accessed in an `instanceof` expression in `t`.
Expand Down
2 changes: 1 addition & 1 deletion java/ql/lib/semmle/code/java/DependencyCounts.qll
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ predicate numDepends(RefType t, RefType dep, int value) {
|
elem = a and usesType(a.getType(), dep)
or
elem = a.getAValue() and
elem = [a.getValue(_), a.getAnArrayValue(_)] and
elem.getFile().getExtension() = "java" and
usesType(elem.(Expr).getType(), dep)
)
Expand Down
67 changes: 31 additions & 36 deletions java/ql/lib/semmle/code/java/JDKAnnotations.qll
Original file line number Diff line number Diff line change
Expand Up @@ -18,76 +18,75 @@ class OverrideAnnotation extends Annotation {
class SuppressWarningsAnnotation extends Annotation {
SuppressWarningsAnnotation() { this.getType().hasQualifiedName("java.lang", "SuppressWarnings") }

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

/** Gets the name of a warning suppressed by this annotation. */
string getASuppressedWarning() { result = this.getASuppressedWarningLiteral().getValue() }
string getASuppressedWarning() { result = this.getAStringArrayValue("value") }
}

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

/**
* DEPRECATED: Getting the field access expression is rarely useful. Use `getATargetElementType()`
* to get the name of the target element.
*
* Gets a target expression within this annotation.
*
* For example, the field access `ElementType.FIELD` is a target expression in
* `@Target({ElementType.FIELD, ElementType.METHOD})`.
*/
Expr getATargetExpression() {
not result instanceof ArrayInit and
(
result = this.getAValue() or
result = this.getAValue().(ArrayInit).getAnInit()
)
}
deprecated Expr getATargetExpression() { result = this.getAnArrayValue("value") }

/**
* Gets the name of a target element type.
*
* For example, `METHOD` is the name of a target element type in
* `@Target({ElementType.FIELD, ElementType.METHOD})`.
*/
string getATargetElementType() {
exists(EnumConstant ec |
ec = this.getATargetExpression().(VarAccess).getVariable() and
ec.getDeclaringType().hasQualifiedName("java.lang.annotation", "ElementType")
|
result = ec.getName()
)
}
string getATargetElementType() { result = this.getAnEnumConstantArrayValue("value").getName() }
}

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

/**
* DEPRECATED: Getting the field access expression is rarely useful. Use `getRetentionPolicy()`
* to get the name of the retention policy.
*
* Gets the retention policy expression within this annotation.
*
* For example, the field access `RetentionPolicy.RUNTIME` is the
* retention policy expression in `@Retention(RetentionPolicy.RUNTIME)`.
*/
Expr getRetentionPolicyExpression() { result = this.getValue("value") }
deprecated Expr getRetentionPolicyExpression() { result = this.getValue("value") }

/**
* Gets the name of the retention policy of this annotation.
*
* For example, `RUNTIME` is the name of the retention policy
* in `@Retention(RetentionPolicy.RUNTIME)`.
*/
string getRetentionPolicy() {
exists(EnumConstant ec |
ec = this.getRetentionPolicyExpression().(VarAccess).getVariable() and
ec.getDeclaringType().hasQualifiedName("java.lang.annotation", "RetentionPolicy")
|
result = ec.getName()
)
}
string getRetentionPolicy() { result = this.getEnumConstantValue("value").getName() }
}

/** A `@Repeatable` annotation. */
class RepeatableAnnotation extends Annotation {
RepeatableAnnotation() { this.getType().hasQualifiedName("java.lang.annotation", "Repeatable") }

/**
* Gets the annotation type which acts as _containing type_, grouping multiple
* repeatable annotations together.
*/
AnnotationType getContainingType() { result = this.getTypeValue("value") }
}

/**
Expand Down Expand Up @@ -119,11 +118,7 @@ abstract class NonReflectiveAnnotation extends Annotation { }

library class StandardNonReflectiveAnnotation extends NonReflectiveAnnotation {
StandardNonReflectiveAnnotation() {
exists(AnnotationType anntp | anntp = this.getType() |
anntp.hasQualifiedName("java.lang", "Override") or
anntp.hasQualifiedName("java.lang", "Deprecated") or
anntp.hasQualifiedName("java.lang", "SuppressWarnings") or
anntp.hasQualifiedName("java.lang", "SafeVarargs")
)
this.getType()
.hasQualifiedName("java.lang", ["Override", "Deprecated", "SuppressWarnings", "SafeVarargs"])
}
}
2 changes: 1 addition & 1 deletion java/ql/lib/semmle/code/java/PrintAst.qll
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ private newtype TPrintAstNode =
shouldPrint(lvde, _) and lvde.getParent() instanceof SingleLocalVarDeclParent
} or
TAnnotationsNode(Annotatable ann) {
shouldPrint(ann, _) and ann.hasAnnotation() and not partOfAnnotation(ann)
shouldPrint(ann, _) and ann.hasDeclaredAnnotation() and not partOfAnnotation(ann)
} or
TParametersNode(Callable c) { shouldPrint(c, _) and not c.hasNoParameters() } or
TBaseTypesNode(ClassOrInterface ty) { shouldPrint(ty, _) } or
Expand Down
10 changes: 3 additions & 7 deletions java/ql/lib/semmle/code/java/UnitTests.qll
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,13 @@ class TestNGTestMethod extends Method {
exists(TestNGTestAnnotation testAnnotation |
testAnnotation = this.getAnAnnotation() and
// The data provider must have the same name as the referenced data provider
result.getDataProviderName() =
testAnnotation.getValue("dataProvider").(StringLiteral).getValue()
result.getDataProviderName() = testAnnotation.getStringValue("dataProvider")
|
// Either the data provider should be on the current class, or a supertype
this.getDeclaringType().getAnAncestor() = result.getDeclaringType()
or
// Or the data provider class should be declared
result.getDeclaringType() =
testAnnotation.getValue("dataProviderClass").(TypeLiteral).getReferencedType()
result.getDeclaringType() = testAnnotation.getTypeValue("dataProviderClass")
)
}
}
Expand Down Expand Up @@ -227,9 +225,7 @@ class TestNGListenersAnnotation extends TestNGAnnotation {
/**
* Gets a listener defined in this annotation.
*/
TestNGListenerImpl getAListener() {
result = this.getAValue("value").(TypeLiteral).getReferencedType()
}
TestNGListenerImpl getAListener() { result = this.getATypeArrayValue("value") }
}

/**
Expand Down
2 changes: 1 addition & 1 deletion java/ql/lib/semmle/code/java/frameworks/JAXB.qll
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class JaxbType extends Class {
this.getAnAnnotation() = a and
a.getType().(JaxbAnnotationType).hasName("XmlAccessorType")
|
result.getAnAccess() = a.getValue("value")
result = a.getEnumConstantValue("value")
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,5 @@ class RunWithAnnotation extends Annotation {
/**
* Gets the runner that will be used.
*/
Type getRunner() { result = this.getValue("value").(TypeLiteral).getReferencedType() }
Type getRunner() { result = this.getTypeValue("value") }
}
6 changes: 1 addition & 5 deletions java/ql/lib/semmle/code/java/frameworks/JaxWS.qll
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,7 @@ class JaxRSProducesAnnotation extends JaxRSAnnotation {
/**
* Gets a declared content type that can be produced by this resource.
*/
Expr getADeclaredContentTypeExpr() {
result = this.getAValue() and not result instanceof ArrayInit
or
result = this.getAValue().(ArrayInit).getAnInit()
}
Expr getADeclaredContentTypeExpr() { result = this.getAnArrayValue("value") }
}

/** An `@Consumes` annotation that describes content types can be consumed by this resource. */
Expand Down
4 changes: 1 addition & 3 deletions java/ql/lib/semmle/code/java/frameworks/MyBatis.qll
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,7 @@ class IbatisSqlOperationAnnotation extends Annotation {
/**
* Gets this annotation's SQL statement string.
*/
string getSqlValue() {
result = this.getAValue("value").(CompileTimeConstantExpr).getStringValue()
}
string getSqlValue() { result = this.getAStringArrayValue("value") }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ class PersistentEntity extends RefType {
}

/**
* Gets the access type for this entity as defined by a `@javax.persistence.Access` annotation, if any.
* Gets the access type for this entity as defined by a `@javax.persistence.Access` annotation,
* if any, in lower case.
*/
string getAccessTypeFromAnnotation() {
exists(AccessAnnotation accessType | accessType = this.getAnAnnotation() |
result =
accessType.getValue("value").(FieldRead).getField().(EnumConstant).getName().toLowerCase()
result = accessType.getEnumConstantValue("value").getName().toLowerCase()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,7 @@ class SpringQualifierDefinitionAnnotation extends Annotation {
/**
* Gets the value of the qualifier field for this qualifier.
*/
string getQualifierValue() {
result = this.getValue("value").(CompileTimeConstantExpr).getStringValue()
}
string getQualifierValue() { result = this.getStringValue("value") }
}

/**
Expand All @@ -325,9 +323,7 @@ class SpringQualifierAnnotation extends Annotation {
/**
* Gets the value of the qualifier field for this qualifier.
*/
string getQualifierValue() {
result = this.getValue("value").(CompileTimeConstantExpr).getStringValue()
}
string getQualifierValue() { result = this.getStringValue("value") }

/**
* Gets the bean definition in an XML file that this qualifier resolves to, if any.
Expand All @@ -350,9 +346,7 @@ class SpringResourceAnnotation extends Annotation {
/**
* Gets the specified name value, if any.
*/
string getNameValue() {
result = this.getValue("name").(CompileTimeConstantExpr).getStringValue()
}
string getNameValue() { result = this.getStringValue("name") }

/**
* Gets the bean definition in an XML file that the resource resolves to, if any.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,10 @@ class SpringComponentScan extends Annotation {
*/
string getBasePackages() {
// "value" and "basePackages" are synonymous, and are simple strings
result = this.getAValue("basePackages").(StringLiteral).getValue()
result = this.getAStringArrayValue(["basePackages", "value"])
or
result = this.getAValue("value").(StringLiteral).getValue()
or
exists(TypeLiteral typeLiteral |
// Base package classes are type literals whose package should be considered a base package.
typeLiteral = this.getAValue("basePackageClasses")
|
result = typeLiteral.getReferencedType().(RefType).getPackage().getName()
)
// Base package classes are type literals whose package should be considered a base package.
result = this.getATypeArrayValue("basePackageClasses").(RefType).getPackage().getName()
}
}

Expand Down Expand Up @@ -144,8 +138,7 @@ class SpringComponent extends RefType {
if exists(this.getComponentAnnotation().getValue("value"))
then
// If the name has been specified in the component annotation, use that.
result =
this.getComponentAnnotation().getValue("value").(CompileTimeConstantExpr).getStringValue()
result = this.getComponentAnnotation().getStringValue("value")
else
// Otherwise use the name of the class, with the initial letter lower cased.
exists(string name | name = this.getName() |
Expand Down Expand Up @@ -204,7 +197,7 @@ class SpringComponent extends RefType {
.getType()
.hasQualifiedName("org.springframework.context.annotation", "Profile")
|
result = profileAnnotation.getAValue("value").(StringLiteral).getValue()
result = profileAnnotation.getAStringArrayValue("value")
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,7 @@ class SpringRequestMappingMethod extends SpringControllerMethod {
}

/** Gets the "value" @RequestMapping annotation value, if present. */
string getValue() {
result = requestMappingAnnotation.getValue("value").(CompileTimeConstantExpr).getStringValue()
}
string getValue() { result = requestMappingAnnotation.getStringValue("value") }

/** Holds if this is considered an `@ResponseBody` method. */
predicate isResponseBody() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ class StrutsActionsAnnotation extends StrutsAnnotation {
/**
* Gets an Action annotation contained in this Actions annotation.
*/
StrutsActionAnnotation getAnAction() { result = this.getAValue("value") }
StrutsActionAnnotation getAnAction() { result = this.getAnArrayValue("value") }
}
2 changes: 1 addition & 1 deletion java/ql/src/AlertSuppressionAnnotations.ql
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class SuppressionAnnotation extends SuppressWarningsAnnotation {
string text;

SuppressionAnnotation() {
text = this.getASuppressedWarningLiteral().getValue() and
text = this.getASuppressedWarning() and
exists(getAnnotationText(text))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,5 @@ where
m.getNumberOfParameters() = 1 and
c.getArgument(0).getType() = p and
p.getATypeArgument() = t and
not exists(RetentionAnnotation a |
t.getAnAnnotation() = a and
a.getAValue().(VarAccess).getVariable().hasName("RUNTIME")
)
t.getRetentionPolicy() != "RUNTIME"
select c, "Call to isAnnotationPresent where no annotation has the RUNTIME retention policy."
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ class SpringControllerRequestMappingGetMethod extends SpringControllerGetMethod
.getType()
.hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and
(
this.getAnAnnotation().getValue("method").(VarAccess).getVariable().getName() = "GET" or
this.getAnAnnotation().getValue("method").(ArrayInit).getSize() = 0 //Java code example: @RequestMapping(value = "test")
this.getAnAnnotation().getAnEnumConstantArrayValue("method").getName() = "GET" or
not exists(this.getAnAnnotation().getAnArrayValue("method")) //Java code example: @RequestMapping(value = "test")
) and
not this.getAParamType().getName() = "MultipartFile"
}
Expand Down
2 changes: 1 addition & 1 deletion java/ql/test/library-tests/annotation-arrays/test.ql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ from Field f, Annotation ann, Expr value, Expr valueChild
where
f.getDeclaringType().fromSource() and
ann = f.getAnAnnotation() and
value = ann.getAValue() and
value = ann.getValue(_) and
valueChild.getParent() = value
select f, ann, value, valueChild
Loading