Java: Add annotation tests

This commit is contained in:
Marcono1234
2022-04-02 18:53:29 +02:00
committed by Chris Smowton
parent 8c9bdeb3be
commit 37b18914ac
11 changed files with 867 additions and 17 deletions

View File

@@ -66,6 +66,16 @@ class Annotation extends @annotation, Expr {
*/
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
@@ -74,7 +84,7 @@ class Annotation extends @annotation, Expr {
* If the element value type is an enum type array, use `getAnEnumConstantArrayValue`.
*/
EnumConstant getEnumConstantValue(string name) {
result = this.getValue(name).(FieldRead).getField()
result = this.getNonArrayValue(name).(FieldRead).getField()
}
/**
@@ -87,20 +97,23 @@ class Annotation extends @annotation, Expr {
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.getValue(name).(CompileTimeConstantExpr).getStringValue()
result = this.getNonArrayValue(name).(CompileTimeConstantExpr).getStringValue()
}
/**
* If the value type of the annotation element with the specified `name` is `int`,
* gets the int value used for that element. This includes default values in case no
* explicit value is specified.
* 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, use `getAnIntArrayValue`.
* 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.getValue(name).(CompileTimeConstantExpr).getIntValue()
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"])
}
/**
@@ -111,7 +124,7 @@ class Annotation extends @annotation, Expr {
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.getValue(name).(CompileTimeConstantExpr).getBooleanValue()
result = this.getNonArrayValue(name).(CompileTimeConstantExpr).getBooleanValue()
}
/**
@@ -121,7 +134,9 @@ class Annotation extends @annotation, Expr {
*
* If the element value type is a `Class` array, use `getATypeArrayValue`.
*/
Type getTypeValue(string name) { result = this.getValue(name).(TypeLiteral).getReferencedType() }
Type getTypeValue(string name) {
result = this.getNonArrayValue(name).(TypeLiteral).getReferencedType()
}
/** Gets the element being annotated. */
Element getTarget() { result = this.getAnnotatedElement() }
@@ -169,13 +184,16 @@ class Annotation extends @annotation, Expr {
/**
* Gets a value of the annotation element with the specified `name`, which must be declared as an `int`
* array. This includes default values in case no explicit value is specified.
* 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()
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[]"])
}
/**
@@ -194,14 +212,16 @@ class Annotation extends @annotation, Expr {
* 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. Otherwise, the result will be the single expression defined for
* the value and the `index` will be 0.
* 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 result = value.(ArrayInit).getInit(index)
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
)
@@ -234,15 +254,21 @@ private predicate sourceAnnotValue(Annotation a, Method m, Expr val) {
/** An abstract representation of language elements that can be annotated. */
class Annotatable extends Element {
/** Holds if this element has an annotation, including inherited annotations. */
/**
* 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. */
/**
* 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.
* annotations. The retention policy of the annotation type is not considered.
*/
predicate hasAnnotation(string package, string name) {
exists(AnnotationType at | at = this.getAnAnnotation().getType() |
@@ -254,6 +280,7 @@ class Annotatable extends Element {
* 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() {
@@ -263,6 +290,7 @@ class Annotatable extends Element {
/**
* 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 }
@@ -302,6 +330,8 @@ class Annotatable extends Element {
* - 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(_) }
@@ -320,6 +350,11 @@ class Annotatable extends Element {
or
this.(NestedClass).getEnclosingType().suppressesWarningsAbout(category)
or
this.(LocalClassOrInterface)
.getLocalTypeDeclStmt()
.getEnclosingCallable()
.suppressesWarningsAbout(category)
or
this.(LocalVariableDecl).getCallable().suppressesWarningsAbout(category)
}
}