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. It 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 &emdash; 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)getSupportedAnnotationTypes
-- (from AbstractProcessor
) returns
a set of strings naming the annotations that javac
will pass to
the processor (e.g., Collections.singleton("*")
)getSupportedSourceVersion
-- (from AbstractProcessor
)
returns the latest source version supported by this processor, typically SourceVersion.RELEASE_7
SourceVisitor
is a wrapper around TreeScanner
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
and isAssignable
methods
that (respectively) check if one type is a subtype of another
(with respect to the annotations on its raw type) and if one type
is assignable to another (with respect to the annotations on the
entire type)SubtypeVisitor
extends SourceVisitor
, providing a
type-checking visitor implementation that currently checks and reports
four 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 typeSubtypeVisitor
may
not be necessary. The @NonNull
type checker, for
instance, does not require a visitor that extends
SubtypeVisitor
.
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.
(The checker for the @Interned
annotation is not yet
distributed; the code snippet here is provided only as an example. Also
see the complete @NonNull
checker that is distributed with the
checkers framework.)
// 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);
}