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