Let’s Build a Compiler Plug-In
The first step is to download and build the current release of the
Java 8 compiler, which supports compiler plug-ins.Toward this end, download
the latest version and follow the build instructions in theREADME
file.
Once the compiler is built, include the generated dist/lib/ classes.jar
file in your project. Alternatively, you can download a ready-made binary from
jdk8.java.net. Next, there are several steps we need to follow to
build our plug-in.
Here is a summary of the steps:
1. Implement the com.sun.source.util.Plugin interface .
2. Add a TaskListener to perform additional
behavior after the type-checking phase.
3. Create an abstract syntax tree (AST) visitor to locate binary expressions:
a. Evaluate whether the left side is a method call expression with a
receiver’s type that is a subtype of java.util.Map.
b. Evaluate whether the right side is a null expression.
Step 1: Implement the com.sun.source.util .Plugin
interface.
The first step is to create the main class, which implements com.sun.source
.util.Plugin. We return the name of our plug-in via the getName() method,
Step 2: Add a TaskListener.
We are looking for a code pattern that checks whether the receiver
of
the method call get() is a subtype of java.util.Map. To get the type of the
receiver, we need information about the types of expressions that are resolved
during the type-checking phase. We therefore need to insert a new phase after type
checking. Toward this end, we first create a TaskListener and add it to the current JavacTask. We now need to create the
class CodePatternTaskListener, which implements a TaskListener. A
TaskListener has two methods, started() and finished(), which are called,
respectively, before and after certain events. These events are encapsulated in a TaskEvent object. In our case, all we
need to do is implement the finished() method and check for an event mentioning the Analyze phase (type checking.
Step 3: Create the AST visitor.
Next, we need to write the logic to locate the code pattern and report
it. How do we do that? Thankfully, a task event provides us with a CompilationUnitTree,which represents the
current source file analyzed in a tree structure. It can be accessed with the getCompilationUnit() method.
Our code will need to traverse this tree, locate a binary node, and
evaluate the node’s left and right children. This sounds like a visitor pattern
job. The Compiler Tree API provides us with a readymade visitor class designed
for
such tasks: com.sun.source.util.TreeScanner<R,
P>. It visits all the nodes
in the AST.First, we initialize our visitor object and then visit the CompilationUnitTree. All we have left to do is
to override visitBinary(BinaryTree
node,P p) and write the logic to
verify the code pattern.
Let’s Run Our Compiler
Plug-In
We are almost finished. The final step is to set up a file called com.sun.source.util.Plugin
located in
META-INF/services/. This file must contain the name of our plug-in, CodePatternPlugin, which allows javac to
load the appropriate plug-in.
Next, using your favorite IDE or the command line, create a Java archive
(JAR) file from your project containing the META-INF directory and compiled
class files:
Finally, you can run the plug-in
■ -processorpath indicates the path where the plug-in JAR file is located
■ -Xplugin indicates the name of the
plug-in to run, which is CodePatternPlugin, in this case
The new plug-in mechanism provides a simple hook to javac. You can
use it to extend javac with new behavior. In addition, you can distribute a
plug-in without making modifications to the javac code base. In this article,
we showed how you can use this mechanism to easily write a
customized
source code analysis tool for your applications
Great post. One question: Is it possible to register the plugin using Maven? I am not sure how to transfer the command line options described above to the pom file.
ReplyDelete