Contents:
This document describes how to write a type-checking compiler plugin that detects potential bugs by making use of JSR 308 annotations. Before reading this document, you should read JSR 308 Checkers and Framework, which explains how to run such a plugin.
A checker plugin is centered around two classes: a checker class and a visitor class.
The checker class is the primary interface to javac
's annotation
processing facility. The checker class provides the hooks invoked by the compiler
during annotation processing, and methods for reporting errors via the
compiler's internal messaging mechanism. The base class for checkers is
checkers.source.SourceChecker
.
Typically, the checker class invokes the visitor class on each input source
file.
The visitor class is a visitor for Java source syntax trees (as provided
by the semi-public Tree API and not the internal javac
tree
representation). The base class for visitors is
checkers.source.SourceVisitor
.
Typically, the visitor class performs type-checking as it walks each source
file's AST.
SourceChecker
has "provider" methods that allow the default
SourceChecker
behavior to be extended — some of these are inherited from Sun's
AbstractProcessor
class, and others are defined in SourceChecker
itself. These methods should be overridden accordingly. They are:
getMessages
– returns a Properties
instance where the keys are
the strings passed to SourceChecker.report
(like
"invalid.assignment") and the values are the strings to be printed
("cannot assign ..."); this can be used to specialize messages when
overriding a checker, and might also be used for
internationalizationgetSourceVisitor
– returns a SourceVisitor
(in this case, the
subclass of SourceVisitor
specific to this plugin)getFactory
– returns a custom subclass of AnnotatedTypeFactory
, if this plugin requires itAdditionally, as recommended by the annotation processing API, checker
classes may be annotated with the SupportedAnnotationTypes
and SupportedSourceVersion
annotations.
SourceVisitor
is a wrapper around TreePathScanner
for performing typechecking using the annotation processing API
and the Annotated*Type classes.
To extend SourceVisitor
, override the appropriate visit*
method
from TreeScanner
(these methods have specific tree nodes for
parameters, i.e., visitAssignment
has an argument of type
AssignmentTree
). The protected member AnnotatedTypeFactory factory
can be used to create AnnotatedClassType
s for querying the
annotations on/in a tree node.
SubtypeChecker
and SubtypeVisitor
in the
checker.subtype
package implement a generic type-checker
for type qualifiers for which the qualified type is the subtype of the
unqualified type. Many type qualifiers, including
@NonNull
and @Interned
, fall into this
category.
SubtypeChecker
extends
SourceChecker
, and it provides two
primary services:
getSourceVisitor
method that returns
an instance of SubtypeVisitor
isSubtype
method
that checks if one type is a subtype of another
with respect to the type qualifier-annotations on the typeSubtypeVisitor
extends SourceVisitor
, providing a
type-checking visitor implementation that currently checks and reports
six errors:
assignment.invalid
) when an
assignment from an unqualified type to a qualified supertype is
foundargument.invalid
) when an
argument with the unqualified type is passed to a method for a
parameter with the qualified typereceiver.invalid
) when a method
whose receiver has the qualified type is called from an object with
the unqualified typereturn.invalid
) when the
expression in a return
statement has the unqualified
type but the method declaration has the qualified return typeoverride.parameter.invalid
) when a parameter in a method
declaration is incompatible with that parameter in the overridden method's
declarationoverride.return.invalid
)
when a parameter in a method declaration is incompatible with that
parameter in the overridden method's declaration
For many type-checkers, overriding SubtypeVisitor
may
not be necessary.
By overriding the getMessages
method, a checker built on
SubtypeChecker
can customize the error messages produced
by the SubtypeVisitor
. The keys for each message are
shown in parentheses in list of errors in the previous section; the overridden
getMessages
method must return a
java.util.Properties
object that maps each key to the
desired error message.
The "Annotated Types" framework in checkers.types
can be used to
obtain annotations on tree nodes. The protected factory
field in
SourceVisitor
has a getClass
method that takes a single tree node
as an argument; the returned AnnotatedClassType
has
isAnnotatedWith
and hasAnnotationAt
methods for querying.
This is an example of a method from an example subclass of
SourceVisitor
for checking a
@Interned
annotation.
// Checks that both the left- and right-hand operands for a
// binary operator "a == b" are @Interned.
@Override
public Void visitBinary(BinaryTree node, Void p) {
if (node.getKind() == Tree.Kind.EQUAL_TO) {
AnnotatedClassType left = factory.getClass(node.getLeftOperand());
AnnotatedClassType right = factory.getClass(node.getRightOperand());
if (!left.isAnnotatedWith(Interned.class) ||
!right.isAnnotatedWith(Interned.class)) {
checker.report(Result.warning("not.interned"), node);
}
}
return super.visitBinary(node, p);
}
This section is relevant only if you wish to add your checker to the source code repository for the checkers framework — for example, if you wish to have your checker distributed with the framework. You may wish to use the @NonNull checker as an example.
First, you need to check out the annotations repository. The checkers and framework is in the annotations/checkers/ directory. The HTML documentation for your checker goes here. The source code goes in annotations/checkers/src/checkers/: define the annotation itself in quals/, and put the checker itself in a directory that is a sibling of quals/, nonnull/, etc.
Back to the JSR 308 checkers framework.