/** * 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() } /** Gets the annotation element with the specified `name`. */ AnnotationElement getAnnotationElement(string name) { result = this.getType().getAnnotationElement(name) } /** * DEPRECATED: Getting the value of _any_ annotation element is error-prone because * it could lead to selecting the value of the wrong element by accident (for example * when an annotation type is extended in the future). Prefer the predicate `getValue(string)` * and explicitly specify the element name. Use `getValue(_)` if it is really desired to * get the value of any element. * * Gets a value of an annotation element. This includes default values in case * no explicit value is specified. For elements with an array value type this * might have an `ArrayInit` as result. To properly handle array values, prefer * the predicate `getAnArrayValue`. */ deprecated Expr getAValue() { filteredAnnotValue(this, _, result) } /** * 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, _) } /** * DEPRECATED: Predicate has been renamed to `getAnArrayValue` */ deprecated Expr getAValue(string name) { result = this.getAnArrayValue(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().getExtension() = "java" } /** 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.nestedName() = 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("")` * 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) } }