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 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 a few "provider" methods that are used by
default implementation -- 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 (i.e., Collections.singleton("*")
)getSupportedSourceVersion
-- (from AbstractProcessor
) should
return SourceVersion.RELEASE_6
(or SourceVersion.RELEASE_7
for OpenJDK)SourceVisitor
is simply a wrapper around TreeScanner
that
maintains protected instances of a few important utility classes
provided by the tree and annotation processing APIs (specifically,
these classes are Trees
, Elements
, and Types
) as well as an
AnnotatedTypeFactory
(see below).
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.
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.)
01 // Checks that both the left- and right-hand operands for a
02 // binary operator are @Interned.
03 @Override
04 public Void visitBinary(BinaryTree node, Void p) {
05 if (node.getKind() == Tree.Kind.EQUAL_TO) {
06 AnnotatedClassType left = factory.getClass(node.getLeftOperand());
07 AnnotatedClassType right = factory.getClass(node.getRightOperand());
08 if (!left.isAnnotatedWith(Interned.class) ||
09 !right.isAnnotatedWith(Interned.class)) {
10 checker.report(Result.warning("not.interned"), node);
11 }
12 }
13 return super.visitBinary(node, p);
14 }