NullAway Documentation

repository·master·Indexed 26 days ago

https://github.com/uber/nullaway

NullAway is a fast, annotation-based static analysis tool for Java designed to eliminate NullPointerExceptions (NPEs). It integrates as a plugin for Error Prone and uses a 'non-null by default' model. The toolset includes jar-infer for nullability inference on external libraries and AstubxGeneratorCLI for converting JSON annotations into .astubx files. It supports integration with Gradle, Android projects, and Lombok, and recommends the use of JSpecify annotations.

Tokens
2.8K
Snippets
9
Records
13
Agent score
88%

What's inside NullAway

  1. Understand NullAway's nullability model

    master

    NullAway operates on a 'non-null by default' assumption. It assumes every method parameter, return value, and field is non-null unless explicitly marked with a @Nullable annotation.

    Workflow for fixing NPEs:

    1. Identify Error: NullAway reports an error when a null is passed to a parameter assumed to be @NonNull.
    2. Annotate: Add @Nullable to the parameter/field to acknowledge it can be null.
    3. Check: NullAway will then flag any dereferencing of that @Nullable variable.
    4. Guard: Add a null check (e.g., if (x != null)) to satisfy the checker.
    // 1. Buggy code (assumes non-null)
    static void log(Object x) {
        System.out.println(x.toString());
    }
    static void foo() {
        log(null); // Error: passing @Nullable parameter 'null' where @NonNull is required
    }
    
    // 2. Fixed with @Nullable and a null check
    static void log(@Nullable Object x) {
        if (x != null) {
            System.out.println(x.toString());
        }
    }
  2. Use NullAway with Lombok

    master

    NullAway has best-effort compatibility with Lombok (e.g., @lombok.Builder and @Data).

    To ensure NullAway correctly recognizes and ignores Lombok-generated code, Lombok must add the @lombok.Generated annotation to its output.

    If you are using a Lombok version older than 1.18.34, you must explicitly enable this in your lombok.config file:

    lombok.addLombokGeneratedAnnotation = true
  3. Configure NullAway for Android projects

    master

    Recent versions of the Gradle Error Prone Plugin (3.0.0+) do not support Android directly. If you are using a recent version, you will need custom configuration to run Error Prone and NullAway in an Android environment.

    For Android, you can use androidx.annotation.Nullable instead of JSpecify annotations. If you are using an older 2.x version of the Gradle Error Prone Plugin, Android support is built-in.

  4. Generate JSpecify JDK astubx file

    master

    To generate an .astubx file from the annotated JSpecify JDK, you must use a two-stage process involving a javac plugin to capture nullability annotations as .json files, followed by the AstubxGeneratorCLI to convert those files into the final .astubx format.

    Prerequisites

    1. Build the required NullAway modules:
      ./gradlew :jdk-javac-plugin:build :jdk-annotations:astubx-generator-cli:build
    2. Clone the JSpecify JDK fork and check out the test branch.

    Step 1: Configure and Build the JSpecify JDK

    In the cloned JDK repository, edit make/common/JavaCompilation.gmk to inject the NullAway plugin:

    1. Add the plugin flag to $1_API_DIGEST_FLAGS. You can specify a custom directory instead of /tmp to store the generated .json files.
    2. Add the absolute path to the jdk-javac-plugin-all.jar (located in your NullAway repo) to $1_AUGMENTED_CLASSPATH.

    Example configuration in JavaCompilation.gmk:

    $1_API_DIGEST_FLAGS += -Xplugin:"NullnessAnnotationSerializer /tmp"
    $1_AUGMENTED_CLASSPATH += /path/to/your/NullAway/jdk-javac-plugin/build/libs/jdk-javac-plugin-all.jar

    Then, build the JDK:

    make clean && make jdk

    Step 2: Convert JSON to ASTUBX

    Run the com.uber.nullaway.jdkannotations.AstubxGeneratorCLI main method (e.g., via IntelliJ). The CLI requires two arguments:

    1. The directory containing the generated .json files.
    2. The destination directory for the output file.

    The resulting file will be named output.astubx.

    Step 3: Install the output

    Copy the generated output.astubx to nullaway/src/main/resources/jspecify-jdk.astubx within your NullAway repository.

  5. Use the NullAway jar-infer CLI tool

    master

    The jar-infer-cli-tool extends NullAway to support nullability inference on external libraries. You can run it using the following command structure:

    java -jar <path-to-jar-infer-cli-tool> -i <in_path> -o <out_path> [-p <pkg_name>] [-vdh]

    java -jar <path-to-jar-infer-cli-tool> -i <in_path> -o <out_path> [-p <pkg_name>] [-vdh]
  6. Install NullAway jar-infer

    master

    To install NullAway jar-infer, clone the repository and use the Gradle wrapper to build the project. This process will automatically pull in the required WALA jars and build the analysis code.

    Requirements:

    • Java 8
    • Gradle build tool
    • WALA analysis framework
    gradle wrapper
    ./gradlew build
  7. Install NullAway in a non-Android Java project using Gradle

    master

    To integrate NullAway into a standard Java project, you must use JDK 17 or higher and Error Prone 2.36.0 or higher. You need to apply the net.ltgt.errorprone plugin, add nullaway and error_prone_core to your errorprone dependencies, and provide a nullability annotation library (JSpecify is recommended).

    Crucially, you must configure NullAway with either AnnotatedPackages or OnlyNullMarked to define which packages are subject to nullability checks. You can also set the severity level using check("NullAway", CheckSeverity.ERROR) to treat nullability issues as build errors.

    plugins {
      // we assume you are already using the Java plugin
      id "net.ltgt.errorprone" version "<plugin version>"
    }
    
    dependencies {
      errorprone "com.uber.nullaway:nullaway:<NullAway version>"
    
      // Some source of nullability annotations; JSpecify recommended,
      // but others supported as well.
      api "org.jspecify:jspecify:1.0.0"
    
      errorprone "com.google.errorprone:error_prone_core:<Error Prone version>"
    }
    
    import net.ltgt.gradle.errorprone.CheckSeverity
    
    tasks.withType(JavaCompile) {
      options.errorprone {
        check("NullAway", CheckSeverity.ERROR)
        option("NullAway:AnnotatedPackages", "com.uber")
      }
      // Include to disable NullAway on test code
      if (name.toLowerCase().contains("test")) {
        options.errorprone {
          disable("NullAway")
        }
      }
    }
  8. Exclude generated code from NullAway checks

    master

    Annotation processors like Dagger or AutoValue may generate code in the same package namespace as your application code. If NullAway is set to ERROR level, errors in this generated code will fail your build.

    To prevent this, use the Error Prone -XepExcludedPaths option to provide a regex that matches the directories containing generated code. In Gradle, use options.errorprone.excludedPaths=.

  9. Update JarInfer Android SDK Models

    master

    If you need to update the JarInfer Android SDK models, follow these steps:

    1. Change the version in gradle.properties to a non-SNAPSHOT version and run ./gradlew build.
    2. Obtain a copy of the AOSP framework_intermediates for the target Android version.
    3. (First time only) Copy the template configuration: cp jar-infer/scripts/android-jar.conf.template jar-infer/scripts/android-jar.conf.
    4. Configure the correct paths and versions in android-jar.conf.
    5. Remove the existing ASTUBX file (e.g., rm jar-infer/android-jarinfer-models-sdk28/src/main/resources/jarinfer.astubx for SDK 28).
    6. Run the update script: python jar-infer/scripts/android-jar.py.
    # Example for SDK 28 cleanup
    rm jar-infer/android-jarinfer-models-sdk28/src/main/resources/jarinfer.astubx
    python jar-infer/scripts/android-jar.py
  10. Release a new version of NullAway

    master

    To perform a formal release of NullAway, follow this sequence:

    1. Synchronize your local master branch: git checkout master && git pull.
    2. Update the version in gradle.properties to a non-SNAPSHOT version.
    3. Update CHANGELOG.md with the release notes.
    4. Commit the changes: git commit -am "Prepare for release X.Y.Z.".
    5. Tag the release: git tag -a vX.Y.Z -m "Version X.Y.Z".
    6. Publish the artifact: ./gradlew clean publish.
    7. Revert gradle.properties to the next SNAPSHOT version.
    8. Commit the version revert: git commit -am "Prepare next development version.".
    9. Push all changes and tags: git push && git push --tags.
    10. Verify the push on GitHub.
    11. Deploy the artifact to the Maven Central Repository.
    12. Create a new release on GitHub using the notes from CHANGELOG.md.
    # Release workflow summary
    git checkout master && git pull
    # ... update gradle.properties and CHANGELOG.md ...
    git commit -am "Prepare for release X.Y.Z."
    git tag -a vX.Y.Z -m "Version X.Y.Z"
    ./gradlew clean publish
    # ... revert to SNAPSHOT ...
    git commit -am "Prepare next development version."
    git push && git push --tags
  11. Publish an unsigned local build

    master

    By default, NullAway requires builds to be signed unless they are SNAPSHOT versions. To publish a non-SNAPSHOT build (such as a LOCAL version) to your local Maven repository without signing, set the ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED environment variable to false when running the publish command.

    ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED=false ./gradlew publishToMavenLocal
  12. Use AstubxGeneratorCLI to convert JSON annotations

    master

    The com.uber.nullaway.jdkannotations.AstubxGeneratorCLI is the entrypoint for converting nullability annotation .json files into an .astubx file.

    It accepts two positional arguments:

    1. Input Directory: The directory containing the .json files generated by the javac plugin.
    2. Output Directory: The directory where the resulting output.astubx file should be placed.