Despite claims to the contrary, the Checker Framework is actually compatible with Lombok. However, a quirk of how Lombok works means that the order of annotation processors matters, and the Checker Framework processors must be listed first. This is confusing because the Checker Framework processors are indended to run last.
This repo demonstrates a functioning project that combines the two.
Install Maven and run mvn verify
to compile the project. It should fail
(expected!) with
[ERROR] /Users/cloncaric/src/checker-framework-plus-lombok/src/main/java/calvin/example/Basics.java:[15,19] error: [dereference.of.nullable] dereference of possibly-null reference t.getVal()
This error indicates that the Checker Framework is working. It shows that the Checker Framework was able to report an error about a misuse of a Lombok-generated method.
You can also demonstrate this with ./compile-by-hand.sh
, which shows a simple
invocation of javac
without all of Maven's machinery.
Javac runs annotation processors before typechecking. Each processor
advertises a set of class-level annotations that it is interested in using the
@SupportedAnnotationTypes
annotation or by overriding
getSupportedAnnotationTypes
. Javac will only run a processor on a class if
that class has a class-level annotation that the processor is interested in. A
processor can advertise interest in all annotations using
@SupportedAnnotationTypes("*")
, in which case it will run on every class,
even if the class has no class-level annotations.
One strange quirk of the annotation processing machinery is that if there are
two processors with @SupportedAnnotationTypes("*")
and the first of them
returns true
from its process
method, then the second is not guaranteed to
run.
Lombok's annotation processor adversises interest in all annotations.
Furthermore, its process
method returns true when all the annotations on the
class are Lombok annotations and there is at least one of them.
The Checker Framework annotation processors advertise interest in all
annotations.
Their process
methods always return false.
However, because javac runs annotation processors before typechecking, their
process methods do not actually run the respective checker. Instead, they
register a callback to be invoked later, after typechecking has completed.
The unfortunate combination of background facts leads to incorrect behavior for classes that have class-level Lombok annotations and no other annotations, e.g.
@lombok.Slf4j
public class Basics {
...
}
For such classes, Lombok returns true from its process
method, preventing the
Checker Framework from running at all. By running the Checker Framework first,
it has a chance to register its callback before Lombok ends javac's annotation
processing. The callback will then run after typechecking, once Lombok has
finished its rewriting work.
There is a second issue related to a bug in javac (JDK-8312460),
although the primary problem is the fact that Lombok's process
method returns
true in some cases (see above).
Lombok includes a second annotation processor called ClaimingProcessor
that
(1) advertises interest in only the lombok.*
annotations and (2) returns true
in its process
method (but does nothing else). This annotation processor is
an ideal, well-formed processor that does not inherit any of the quirks of
@SupportedAnnotationTypes("*")
processors.
Due to JDK-8312460, even if the Lombok team patched their process
method to
always return false, ClaimingProcessor
would still prevent the Checker
Framework from running. See the JDK-8312460 description linked above for more
information.
Unfortunately the ticket was closed as "won't fix", although I suspect that the javac developers are very busy and did not fully understand this entirely-too-subtle issue.