Demonstrates capabilities of some java tools to analyze (potential) null references. These tools are:
Open the various java sources and read the comments to have an understanding of various checks that can be done (or not) by these tools.
Feature | Eclipse IDE 4.7.1 or jdt | Checker Framework | IntelliJ 2017.2.5 |
---|---|---|---|
IDE support | :white_check_mark: | :white_check_mark: using plugin | :white_check_mark: |
Command line support | :white_check_mark: | ✅ | To be tested |
Java 8 type annotations support | ✅ | ✅ | ✅ |
Multiple annotations classes supported | ✅ | ✅ | ✅ |
External annotations support | ✅ using .eea files | :white_check_mark: using stubs files | ✅ using xml files |
External annotations provided for common libraries | :red_circle: community effort with lastnpe.org | ✅ JDK, Guava | :white_check_mark: JDK |
IDE support to create external annotations | ✅ | :white_check_mark: | |
Treat all types as @Nonnull by default, unless annotated wih @Nullable | :white_check_mark: using @NonNullByDefault for each package, allows to define the scope: field, parameters, return, generic types, etc... | :white_check_mark: by default, customizable with @DefaultQualifier | :white_check_mark: |
@Polynull support | :red_circle: | :white_check_mark: using @PolyNull | ✅ using @Contract, for instance @Contract("!null->!null;null->null") |
Method contract support (e.g. handle if(StringUtils.hasText(str)) {str...} |
🔴 | ❓ | ✅ using @Contract |
Automatic inference of nullability constraints in external libraries | 🔴 | 🔴 | ✅ for @NonNull and some @Contract. Disabled for @Nullable due to too many false positives |
Treat main, test or generated sources differently | :white_check_mark: | ✅ |
Last version tested: eclipse 4.7.1
The best analysis is performed using external annotations.
This repository/project contains a (non-exhaustive but growing) number of external annotations for usual classes (e.g. Map, List, some guava classes...).
To activate the null reference analysis in this example project, copy ide-settings/eclipse-no-npe-analysis/org.eclipse.jdt.core.prefs
to the .settings/
directory.
To automatically associate an "annotation path" with the "Maven Dependencies" and "JRE" libraries in eclipse build path:
- install eclipse-external-annotations-m2e-plugin from this p2 repository.
- add a maven property
m2e.jdt.annotationpath
in your pom, as demonstrated in pom.xml. - perform a full
Maven/Update project...
in eclipse.
Tip: place the property in a m2e
profile activated only inside eclipse, not in the command-line (see below for command-line usage).
Using a source project for external annotations in the same eclipse workspace allows to quickly add missing annotations directly from eclipse.
<profile>
<id>m2e</id>
<activation>
<property>
<name>m2e.version</name>
</property>
</activation>
<properties>
<!-- the following is effective if the eclipse-external-annotations-m2e-plugin is installed and the eclipse-external-annotations project is open in the same workspace -->
<m2e.jdt.annotationpath>/eclipse-external-annotations/src/main/resources</m2e.jdt.annotationpath>
</properties>
</profile>
To perform the same null reference analysis as the eclipse IDE during a maven build, the jdt compiler must be used in place of the default javac, as demonstrated in the jdt
maven profile of pom.xml.
<profile>
<id>jdt</id>
<properties>
<tycho-version>0.26.0</tycho-version>
</properties>
<repositories>
<repository>
<!-- just to retrieve snapshots of com.github.sylvainlaurent:null-pointer-analysis-examples. Useless if using versions released to maven central -->
<id>ossrh-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
<snapshots>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.sylvainlaurent</groupId>
<artifactId>eclipse-external-annotations</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<!-- check nullability using jdt compiler, only for compile, not for testCompile -->
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<compilerId>jdt</compilerId>
<compilerArgs>
<arg>-properties</arg>
<arg>${project.basedir}/ide-settings/eclipse-with-npe-analysis/org.eclipse.jdt.core.prefs</arg>
<arg>-annotationpath</arg>
<arg>CLASSPATH</arg>
</compilerArgs>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-jdt</artifactId>
<version>${tycho-version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
To launch the compilation with this jdt profile, run: mvn clean test -P jdt
. The compiler should find the same errors as in eclipse:
$ mvn clean test -P jdt
...
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /Users/slaurent/Developer/repos/null-pointer-analysis-examples/src/main/java/packageNonNull/ClassInAnnotatedPackage.java:[13]
echo(null);
^^^^
Null type mismatch: required '@NonNull String' but the provided value is null
[ERROR] /Users/slaurent/Developer/repos/null-pointer-analysis-examples/src/main/java/test/EverythingNonNullByDefault.java:[25]
public EverythingNonNullByDefault(String name) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The @NonNull field address may not have been initialized
[ERROR] /Users/slaurent/Developer/repos/null-pointer-analysis-examples/src/main/java/test/EverythingNonNullByDefault.java:[47]
return city;
^^^^
...
WIP
Though the Checker Framework proposes many checks, we only consider its Nullness checker for the purpose of this example project.
In this example project, the checker-framework
maven profile allows to use this nullness checker:
<profile>
<id>checker-framework</id>
<dependencies>
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker</artifactId>
<version>${checker-framework.version}</version>
</dependency>
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>jdk8</artifactId>
<version>${checker-framework.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<fork>true</fork>
<annotationProcessors>
<!-- Add all the checkers you want to enable here -->
<annotationProcessor>org.checkerframework.checker.nullness.NullnessChecker</annotationProcessor>
</annotationProcessors>
<compilerArgs>
<!-- location of the annotated JDK, which comes from a Maven dependency -->
<arg>-Xbootclasspath/p:${org.checkerframework:jdk8:jar}</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<!-- This plugin will set properties values using dependency information -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<goals>
<goal>properties</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
To launch the compilation with this profile, run: mvn clean test -P checker-framework
. The compiler should find errors:
$ mvn clean test -P checker-framework
...
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /Users/slaurent/Developer/repos/null-pointer-analysis-examples/src/main/java/packageNotAnnotated/ClassInNotAnnotatedPackage.java:[10,13] error: [argument.type.incompatible] incompatible types in argument.
[ERROR] found : null
required: @Initialized @NonNull String
/Users/slaurent/Developer/repos/null-pointer-analysis-examples/src/main/java/packageNotAnnotated/ClassInNotAnnotatedPackage.java:[16,19] error: [return.type.incompatible] incompatible types in return.
[ERROR] found : null
required: @Initialized @NonNull String
/Users/slaurent/Developer/repos/null-pointer-analysis-examples/src/main/java/packageNonNull/ClassInAnnotatedPackage.java:[13,7] error: [argument.type.incompatible] incompatible types in argument.
[ERROR] found : null
required: @Initialized @NonNull String
/Users/slaurent/Developer/repos/null-pointer-analysis-examples/src/main/java/packageNonNull/ClassInAnnotatedPackage.java:[19,10] error: [return.type.incompatible] incompatible types in return.
[ERROR] found : null
required: @Initialized @NonNull String
/Users/slaurent/Developer/repos/null-pointer-analysis-examples/src/main/java/test/EverythingNonNullByDefault.java:[25,11] error: [initialization.fields.uninitialized] the constructor does not initialize fields: address
...