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. 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.

Extending SourceChecker

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:

Additionally, as recommended by the annotation processing API, checker classes may be annotated with the SupportedAnnotationTypes and SupportedSourceVersion annotations.

Extending SourceVisitor

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

Using SubtypeChecker and SubtypeVisitor

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:

SubtypeVisitor extends SourceVisitor, providing a type-checking visitor implementation that currently checks and reports six errors:

For many type-checkers, overriding SubtypeVisitor may not be necessary.

Customizing SubtypeVisitor Error Messages

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.

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.

  // 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);
  }

Putting your checker in the repository

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.