mirror of
https://github.com/github/codeql.git
synced 2025-12-19 10:23:15 +01:00
445 lines
19 KiB
Plaintext
445 lines
19 KiB
Plaintext
/**
|
|
* 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.
|
|
*/
|
|
|
|
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) }
|
|
}
|