Movatterモバイル変換


[0]ホーム

URL:


CodeQL documentation
CodeQL resources

Annotations in Java and Kotlin

CodeQL databases of Java/Kotlin projects contain information about all annotations attached to program elements.

About working with annotations

Annotations are represented by these CodeQL classes:

  • The classAnnotatable represents all entities that may have an annotation attached to them (that is, packages, reference types, fields, methods, and local variables).

  • The classAnnotationType represents a Java annotation type, such asjava.lang.Override; annotation types are interfaces.

  • The classAnnotationElement represents an annotation element, that is, a member of an annotation type.

  • The classAnnotation represents an annotation such as@Override; annotation values can be accessed through member predicategetValue.

For example, the Java/Kotlin standard library defines an annotationSuppressWarnings that instructs the compiler not to emit certain kinds of warnings:

packagejava.lang;public@interfaceSuppressWarnings{String[]value;}

SuppressWarnings is represented as anAnnotationType, withvalue as its onlyAnnotationElement.

A typical usage ofSuppressWarnings would be this annotation for preventing a warning about using raw types:

classA{@SuppressWarnings("rawtypes")publicA(java.util.Listrawlist){}}

The expression@SuppressWarnings("rawtypes") is represented as anAnnotation. The string literal"rawtypes" is used to initialize the annotation elementvalue, and its value can be extracted from the annotation by means of thegetValue predicate.

We could then write this query to find all@SuppressWarnings annotations attached to constructors, and return both the annotation itself and the value of itsvalue element:

importjavafromConstructorc,Annotationann,AnnotationTypeanntpwhereann=c.getAnAnnotation()andanntp=ann.getType()andanntp.hasQualifiedName("java.lang","SuppressWarnings")selectann,ann.getValue("value")

If the codebase you are analyzing uses the@SuppressWarnings annotation, you can check thevalues of the annotation element returned by the query. They should use the"rawtypes" value described above.

As another example, this query finds all annotation types that only have a single annotation element, which has namevalue:

importjavafromAnnotationTypeanntpwhereforex(AnnotationElementelt|elt=anntp.getAnAnnotationElement()|elt.getName()="value")selectanntp

Example: Finding missing@Override annotations

In newer versions of Java, it’s recommended (though not required) that you annotate methods that override another method with an@Override annotation. These annotations, which are checked by the compiler, serve as documentation, and also help you avoid accidental overloading where overriding was intended.

For example, consider this example program:

classSuper{publicvoidm(){}}classSub1extendsSuper{@Overridepublicvoidm(){}}classSub2extendsSuper{publicvoidm(){}}

Here, bothSub1.m andSub2.m overrideSuper.m, but onlySub1.m is annotated with@Override.

We’ll now develop a query for finding methods likeSub2.m that should be annotated with@Override, but are not.

As a first step, let’s write a query that finds all@Override annotations. Annotations are expressions, so their type can be accessed usinggetType. Annotation types, on the other hand, are interfaces, so their qualified name can be queried usinghasQualifiedName. Therefore we can implement the query like this:

importjavafromAnnotationannwhereann.getType().hasQualifiedName("java.lang","Override")selectann

As always, it is a good idea to try this query on a CodeQL database for a Java/Kotlin project to make sure it actually produces some results. On the earlier example, it should find the annotation onSub1.m. Next, we encapsulate the concept of an@Override annotation as a CodeQL class:

classOverrideAnnotationextendsAnnotation{OverrideAnnotation(){this.getType().hasQualifiedName("java.lang","Override")}}

This makes it very easy to write our query for finding methods that override another method, but don’t have an@Override annotation: we use predicateoverrides to find out whether one method overrides another, and predicategetAnAnnotation (available on anyAnnotatable) to retrieve some annotation.

importjavafromMethodoverriding,Methodoverriddenwhereoverriding.overrides(overridden)andnotoverriding.getAnAnnotation()instanceofOverrideAnnotationselectoverriding,"Method overrides another method, but does not have an @Override annotation."

In practice, this query may yield many results from compiled library code, which aren’t very interesting. It’s therefore a good idea to add another conjunctoverriding.fromSource() to restrict the result to only report methods for which source code is available.

Example: Finding calls to deprecated methods

As another example, we can write a query that finds calls to methods marked with a@Deprecated annotation.

For example, consider this example program:

classA{@Deprecatedvoidm(){}@Deprecatedvoidn(){m();}voidr(){m();}}

Here, bothA.m andA.n are marked as deprecated. Methodsn andr both callm, but note thatn itself is deprecated, so we probably should not warn about this call.

As in the previous example, we’ll start by defining a class for representing@Deprecated annotations:

classDeprecatedAnnotationextendsAnnotation{DeprecatedAnnotation(){this.getType().hasQualifiedName("java.lang","Deprecated")}}

Now we can define a class for representing deprecated methods:

classDeprecatedMethodextendsMethod{DeprecatedMethod(){this.getAnAnnotation()instanceofDeprecatedAnnotation}}

Finally, we use these classes to find calls to deprecated methods, excluding calls that themselves appear in deprecated methods:

importjavafromCallcallwherecall.getCallee()instanceofDeprecatedMethodandnotcall.getCaller()instanceofDeprecatedMethodselectcall,"This call invokes a deprecated method."

In our example, this query flags the call toA.m inA.r, but not the one inA.n.

For more information about the classCall, see “Navigating the call graph.”

Improvements

The Java/Kotlin standard library provides another annotation typejava.lang.SupressWarnings that can be used to suppress certain categories of warnings. In particular, it can be used to turn off warnings about calls to deprecated methods. Therefore, it makes sense to improve our query to ignore calls to deprecated methods from inside methods that are marked with@SuppressWarnings("deprecation").

For instance, consider this slightly updated example:

classA{@Deprecatedvoidm(){}@Deprecatedvoidn(){m();}@SuppressWarnings("deprecation")voidr(){m();}}

Here, the programmer has explicitly suppressed warnings about deprecated calls inA.r, so our query should not flag the call toA.m any more.

To do so, we first introduce a class for representing all@SuppressWarnings annotations where the stringdeprecation occurs among the list of warnings to suppress:

classSuppressDeprecationWarningAnnotationextendsAnnotation{SuppressDeprecationWarningAnnotation(){this.getType().hasQualifiedName("java.lang","SuppressWarnings")andthis.getAStringArrayValue("value").regexpMatch(".*deprecation.*")}}

Here, we usegetAStringArrayValue("value") to retrieve any of the suppressed warnings:@SuppressWarnings defines the warnings to suppress using the annotation element namedvalue of typeString[], andgetAStringArrayValue retrieves all of the array values; the CodeQL classAnnotation also has similar convenience predicates for the other possible annotation element types. Afterwards we check whether one of the values is the stringdeprecation using a regular expression match.

For real-world use, this check would have to be generalized a bit: for example, the OpenJDK Java compiler allows@SuppressWarnings("all") annotations to suppress all warnings. We may also want to make sure thatdeprecation is matched as an entire word, and not as part of another word, by changing the regular expression to".*\\bdeprecation\\b.*".

Now we can extend our query to filter out calls in methods carrying aSuppressDeprecationWarningAnnotation:

importjava// Insert the class definitions from abovefromCallcallwherecall.getCallee()instanceofDeprecatedMethodandnotcall.getCaller()instanceofDeprecatedMethodandnotcall.getCaller().getAnAnnotation()instanceofSuppressDeprecationWarningAnnotationselectcall,"This call invokes a deprecated method."

It’s fairly common for projects to contain calls to methods that appear to be deprecated.

Further reading


[8]ページ先頭

©2009-2025 Movatter.jp