MMS • Johan Janssen
Article originally posted on InfoQ. Visit InfoQ
Error Prone, a Java compiler plugin open sourced by Google, performs static analysis during compilation to detect bugs or suggest possible improvements. The plugin contains more than 500 pre-defined bug checks and allows third party and custom plugins. After detecting issues, Error Prone can display a warning or automatically change the code with a predefined solution. Error Prone supports Java 8, 11 and 17 and may be used for bug fixing or large scale refactoring.Installation and configuration instructions for Maven, Bazel, Ant or Gradle can be found in the documentation. The compiler should be configured to use Error Prone as an annotation processor, for example when creating a test project with Maven:
org.apache.maven.plugins
maven-compiler-plugin
3.10.1
17
UTF-8
-XDcompilePolicy=simple
-Xplugin:ErrorProne
com.google.errorprone
error_prone_core
2.15.0
Now an example class can be created. Consider the following method that uses equals
to compare two arrays, more precisely it compares the objects and not the content of the arrays:
public boolean compare(String firstList[], String secondList[]) {
return firstList.equals(secondList);
}
Executing mvn clean verify
triggers the Error Prone analysis and results in the following error message:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.10.1:
compile (default-compile) on project ErrorProne: Compilation failure
[ERROR] …/ErrorProne/src/main/java/org/example/Main.java:[5,28]
[ArrayEquals] Reference equality used to compare arrays
[ERROR] (see https://errorprone.info/bugpattern/ArrayEquals)
[ERROR] Did you mean 'return Arrays.equals(firstList, secondList);'?
The ArrayEquals bug pattern is found and Error Prone suggests to change the implementation in order to compare the content of the array instead of the objects:
return Arrays.equals(firstList, secondList);
Receiving an error helps to improve the code, but it’s also possible to let Error Prone apply the solution automatically. The -XepPatchChecks
argument should contain a comma separated list of bug patterns to apply. In this case, only the ArrayEquals solution is applied to the codebase. The -XepPatchLocation
argument is used to specify the location of the solution file. In this case the original file is modified:
-XDcompilePolicy=simple
-Xplugin:ErrorProne -XepPatchChecks:ArrayEquals
-XepPatchLocation:IN_PLACE
Now, after executing mvn clean verify
the class file is automatically changed to:
public boolean compare(String firstList[], String secondList[]) {
return Arrays.equals(firstList, secondList);
}
The documentation provides more information about the command-line flags.
Apart from the built-in bug patterns, it’s also possible to use patterns from other organizations such as SLF4J or to create a custom plugin. The source code for the built-in rules provides various examples that may be used as the basis for a new plugin. For example, a custom Error Prone plugin can replace the older @Before
JUnit annotation with the new JUnit 5 @BeforeEach
annotation.
The custom Error Prone plugin should be placed in another Maven module than the example project shown earlier. Error Prone uses the service loader mechanism to load the bug checks. Normally, that requires some configuration, however Google’s AutoService project simplifies the configuration by using the @AutoService
annotation. The @BugPattern
annotation is used to configure the name, summary and severity of the bug. The following example returns Description.NO_MATCH
if no @Before
annotation is found or the SuggestedFix
which replaces the @Before
annotation with an @BeforeEach
annotation:
@AutoService(BugChecker.class)
@BugPattern(
name = "BeforeCheck",
summary = "JUnit 4's @Before is replaced by JUnit 5's @BeforeEach",
severity = BugPattern.SeverityLevel.SUGGESTION
)
public class BeforeCheck extends BugChecker implements BugChecker.AnnotationTreeMatcher {
private static final Matcher matcher =
isType("org.junit.Before");
@Override
public Description matchAnnotation(AnnotationTree annotationTree,
VisitorState visitorState) {
if (!matcher.matches(annotationTree, visitorState)) {
return Description.NO_MATCH;
}
return describeMatch(annotationTree,
SuggestedFix.replace(annotationTree, "@BeforeEach"));
}
}
Error Prone and AutoService dependencies are required to build the custom Error Prone plugin:
com.google.errorprone
error_prone_annotations
2.15.0
com.google.errorprone
error_prone_check_api
2.15.0
com.google.auto.service
auto-service-annotations
1.0.1
The AutoService should be configured as an annotation processor:
com.google.auto.service
auto-service
1.0.1
Now the custom Error Prone plugin can be installed to the local Maven repository with the mvn install
command. After executing the command, the example project should be configured to use the new custom plugin as an annotation processor:
org.example.custom.plugin
ErrorProneBeforeCheck
1.0-SNAPSHOT
The new BeforeCheck
should be added to the Error Prone analysis:
-XDcompilePolicy=simple
-Xplugin:ErrorProne -XepPatchChecks:BeforeCheck
-XepPatchLocation:IN_PLACE
An example Test class may be added which contains a mix of both @Before
and @BeforeEach
annotations:
public class ErrorProneTest {
@Before
void before() {
}
@BeforeEach
void beforeEach() {
}
}
When running mvn verify
the new custom Error Prone plugin replaces the @Before
annotation with the @BeforeEach
annotation:
public class ErrorProneTest {
@BeforeEach
void before() {
}
@BeforeEach
void beforeEach() {
}
}
Error Prone uses Java internals, that are nowadays hidden, which might result in errors such as:
java.lang.IllegalAccessError: class com.google.errorprone.BaseErrorProneJavaCompiler
(in unnamed module @0x1a6cf771)
cannot access class com.sun.tools.javac.api.BasicJavacTask (in module jdk.compiler)
because module jdk.compiler does not export
com.sun.tools.javac.api to unnamed module @0x1a6cf771
The solution for Maven is to expose the Java internals by creating a directory called .mvn in the root of the project containing a jvm.config file with the following content:
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
Alternatively the --add-exports
and --add-opens
configuration may be supplied to the Maven Compiler Plugin’s arguments in the pom.xml:
org.apache.maven.plugins
maven-compiler-plugin
3.10.1
--add-exports
jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
…
More information about using Error Prone with Bazel, Ant and Gradle can be found in the installation instructions.