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