How to write a JSR 308 checker plugin

Contents:

Introduction

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.

Components of a plugin: checker class and visitor class

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.

Extending SourceChecker

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:

Extending SourceVisitor

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 AnnotatedClassTypes for querying the annotations on/in a tree node.

Using Annotated*Type

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.

Example: Interned

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  }