Movatterモバイル変換


[0]ホーム

URL:


CodeQL documentation
CodeQL resources

Javadoc

You can use CodeQL to find errors in Javadoc comments in Java code.

About analyzing Javadoc

To access Javadoc associated with a program element, we use member predicategetDoc of classElement, which returns aDocumentable. ClassDocumentable, in turn, offers a member predicategetJavadoc to retrieve the Javadoc attached to the element in question, if any.

Javadoc comments are represented by classJavadoc, which provides a view of the comment as a tree ofJavadocElement nodes. EachJavadocElement is either aJavadocTag, representing a tag, or aJavadocText, representing a piece of free-form text.

The most important member predicates of classJavadoc are:

  • getAChild - retrieves a top-levelJavadocElement node in the tree representation.

  • getVersion - returns the value of the@version tag, if any.

  • getAuthor - returns the value of the@author tag, if any.

For example, the following query finds all classes that have both an@author tag and a@version tag, and returns this information:

importjavafromClassc,Javadocjdoc,stringauthor,stringversionwherejdoc=c.getDoc().getJavadoc()andauthor=jdoc.getAuthor()andversion=jdoc.getVersion()selectc,author,version

JavadocElement defines member predicatesgetAChild andgetParent to navigate up and down the tree of elements. It also provides a predicategetTagName to return the tag’s name, and a predicategetText to access the text associated with the tag.

We could rewrite the above query to use this API instead ofgetAuthor andgetVersion:

importjavafromClassc,Javadocjdoc,JavadocTagauthorTag,JavadocTagversionTagwherejdoc=c.getDoc().getJavadoc()andauthorTag.getTagName()="@author"andauthorTag.getParent()=jdocandversionTag.getTagName()="@version"andversionTag.getParent()=jdocselectc,authorTag.getText(),versionTag.getText()

TheJavadocTag has several subclasses representing specific kinds of Javadoc tags:

  • ParamTag represents@param tags; member predicategetParamName returns the name of the parameter being documented.

  • ThrowsTag represents@throws tags; member predicategetExceptionName returns the name of the exception being documented.

  • AuthorTag represents@author tags; member predicategetAuthorName returns the name of the author.

Example: Finding spurious @param tags

As an example of using the CodeQL Javadoc API, let’s write a query that finds@param tags that refer to a non-existent parameter.

For example, consider this program:

classA{/**    * @param lst a list of strings    */publicStringget(List<String>list){returnlist.get(0);}}

Here, the@param tag onA.get misspells the name of parameterlist aslst. Our query should be able to find such cases.

To begin with, we write a query that finds all callables (that is, methods or constructors) and their@param tags:

importjavafromCallablec,ParamTagptwherec.getDoc().getJavadoc()=pt.getParent()selectc,pt

It’s now easy to add another conjunct to thewhere clause, restricting the query to@param tags that refer to a non-existent parameter: we simply need to require that no parameter ofc has the namept.getParamName().

importjavafromCallablec,ParamTagptwherec.getDoc().getJavadoc()=pt.getParent()andnotc.getAParameter().hasName(pt.getParamName())selectpt,"Spurious @param tag."

Example: Finding spurious @throws tags

A related, but somewhat more involved, problem is finding@throws tags that refer to an exception that the method in question cannot actually throw.

For example, consider this Java program:

importjava.io.IOException;classA{/**    * @throws IOException thrown if some IO operation fails    * @throws RuntimeException thrown if something else goes wrong    */publicvoidfoo(){// ...}}

Notice that the Javadoc comment ofA.foo documents two thrown exceptions:IOException andRuntimeException. The former is clearly spurious:A.foo doesn’t have athrowsIOException clause, and therefore can’t throw this kind of exception. On the other hand,RuntimeException is an unchecked exception, so it can be thrown even if there is no explicitthrows clause listing it. So our query should flag the@throws tag forIOException, but not the one forRuntimeException.

Remember that the CodeQL library represents@throws tags using classThrowsTag. This class doesn’t provide a member predicate for determining the exception type that is being documented, so we first need to implement our own version. A simple version might look like this:

RefTypegetDocumentedException(ThrowsTagtt){result.hasName(tt.getExceptionName())}

Similarly,Callable doesn’t come with a member predicate for querying all exceptions that the method or constructor may possibly throw. We can, however, implement this ourselves by usinggetAnException to find allthrows clauses of the callable, and then usegetType to resolve the corresponding exception types:

predicatemayThrow(Callablec,RefTypeexn){exn.getASupertype*()=c.getAnException().getType()}

Note the use ofgetASupertype* to find both exceptions declared in athrows clause and their subtypes. For instance, if a method has athrowsIOException clause, it may throwMalformedURLException, which is a subtype ofIOException.

Now we can write a query for finding all callablesc and@throws tagstt such that:

  • tt belongs to a Javadoc comment attached toc.

  • c can’t throw the exception documented bytt.

importjava// Insert the definitions from abovefromCallablec,ThrowsTagtt,RefTypeexnwherec.getDoc().getJavadoc()=tt.getParent+()andexn=getDocumentedException(tt)andnotmayThrow(c,exn)selecttt,"Spurious @throws tag."

Improvements

Currently, there are two problems with this query:

  1. getDocumentedException is too liberal: it will returnany reference type with the right name, even if it’s in a different package and not actually visible in the current compilation unit.

  2. mayThrow is too restrictive: it doesn’t account for unchecked exceptions, which do not need to be declared.

To see why the former is a problem, consider this program:

classIOExceptionextendsException{}classB{/** @throws IOException an IO exception */voidbar()throwsIOException{}}

This program defines its own classIOException, which is unrelated to the classjava.io.IOException in the standard library: they are in different packages. OurgetDocumentedException predicate doesn’t check packages, however, so it will consider the@throws clause to refer to bothIOException classes, and thus flag the@param tag as spurious, sinceB.bar can’t actually throwjava.io.IOException.

As an example of the second problem, methodA.foo from our previous example was annotated with a@throwsRuntimeException tag. Our current version ofmayThrow, however, would think thatA.foo can’t throw aRuntimeException, and thus flag the tag as spurious.

We can makemayThrow less restrictive by introducing a new class to represent unchecked exceptions, which are just the subtypes ofjava.lang.RuntimeException andjava.lang.Error:

classUncheckedExceptionextendsRefType{UncheckedException(){this.getASupertype*().hasQualifiedName("java.lang","RuntimeException")orthis.getASupertype*().hasQualifiedName("java.lang","Error")}}

Now we incorporate this new class into ourmayThrow predicate:

predicatemayThrow(Callablec,RefTypeexn){exninstanceofUncheckedExceptionorexn.getASupertype*()=c.getAnException().getType()}

FixinggetDocumentedException is more complicated, but we can easily cover three common cases:

  1. The@throws tag specifies the fully qualified name of the exception.

  2. The@throws tag refers to a type in the same package.

  3. The@throws tag refers to a type that is imported by the current compilation unit.

The first case can be covered by changinggetDocumentedException to use the qualified name of the@throws tag. To handle the second and the third case, we can introduce a new predicatevisibleIn that checks whether a reference type is visible in a compilation unit, either by virtue of belonging to the same package or by being explicitly imported. We then rewritegetDocumentedException as:

predicatevisibleIn(CompilationUnitcu,RefTypetp){cu.getPackage()=tp.getPackage()orexists(ImportTypeit|it.getCompilationUnit()=cu|it.getImportedType()=tp)}RefTypegetDocumentedException(ThrowsTagtt){result.getQualifiedName()=tt.getExceptionName()or(result.hasName(tt.getExceptionName())andvisibleIn(tt.getFile(),result))}

This query should find many fewer, more interesting results.

Currently,visibleIn only considers single-type imports, but you could extend it with support for other kinds of imports.

Further reading


[8]ページ先頭

©2009-2025 Movatter.jp