Skip to content

ucr-riple/NullAwayAnnotator

Repository files navigation

NullAwayAnnotator Build Status

NullAwayAnnotator, or simply Annotator, is a tool that automatically infers nullability types in the given source code and injects the corresponding annotations to pass NullAway checks.

Applying NullAway to build systems requires manual effort in annotating the source code. Even if the code is free of nullability errors, annotations are still needed to pass NullAway checks. A tool that can automatically infer types in the source code and inject the corresponding annotations to pass NullAway checks can significantly reduce the effort of integrating NullAway into build systems.

Annotator minimizes the number of reported NullAway errors by inferring nullability types of elements in the source code and injecting the corresponding annotations. For errors that are not resolvable with any annotations, Annotator injects appropriate suppression annotations. The final output of Annotator is a source code that passes NullAway checks with no remaining errors.

Code Example

In the code below, NullAway reports five warnings.

class Test{
    Object f1 = null; // warning: assigning @Nullable expression to @NonNull field
    Object f2 = null; // warning: assigning @Nullable expression to @NonNull field
    Object f3 = null; // warning: assigning @Nullable expression to @NonNull field
    Object f4 = null; // warning: assigning @Nullable expression to @NonNull field
    Object f5 = f4;
    Object f6 = new Object();
    
    String m1(){
        return f1 != null ? f1.toString() : f2.toString() + f6.toString();
    }
    
    int m2(){
        return f3 != null ? f3.hashCode() : f2.hashCode() + f6.hashCode();
    }
    
    Object m3(){
        return f5;
    }
    
    void m4(){
         f6 = null; // warning: assigning @Nullable expression to @NonNull field
    }
}

Annotator can infer the nullable types in the code above and inject the corresponding annotations. For unresolved errors, suppression annotations are injected. The output below shows the result of running Annotator on the code above.

import javax.annotation.Nullable; // added by Annotator
import org.jspecify.annotations.NullUnmarked; // added by Annotator

class Test{
    @Nullable Object f1 = null;
    @SuppressWarnings("NullAway") Object f2 = null; // inferred to be @Nonnull, and null assignment is suppressed.
    @Nullable Object f3 = null;
    @Nullable Object f4 = null;
    @Nullable Object f5 = f4;
    Object f6 = new Object();  // inferred to be @Nonnull
    
    String m1(){
        return f1 != null ? f1.toString() : f2.toString() + f6.toString();
    }
    
    int m2(){
        return f3 != null ? f3.hashCode() : f2.hashCode() + f6.hashCode();
    }
    
    @Nullable Object m3(){ // inferred to be @Nullable as a result of f5 being @Nullable.
        return f5;
    }
    
    @NullUnmarked //f6 is inferred to be @Nonnull, but it is assigned to null. The error is suppressed by @NullUnmarked.
    void m4(){
         f6 = null; 
    }
}

Annotator propagates the effects of a change throughout the entire module and injects several follow-up annotations to fully resolve a specific warning. It is also capable of processing modules within monorepos, taking into account the modules public APIs and the impacts of annotations on downstream dependencies for improved results.

Installation

We ship Annotator on Maven, as a JAR. You can find the artifact information below -

GROUP: edu.ucr.cs.riple.annotator
ID: annotator-core
ID: annotator-scanner

Using Annotator on a target Java Project

This sections describes how to run Annotator on any project.

  • Requirements for the Target Project

    Dependencies

    • NullAway checker must be activated with version >= 0.10.10
    • AnnotatorScanner checker must be activated with version >= 1.3.6, see more about AnnotatorScanner here.

    Error Prone Flags

    Since Nullaway is built as a plugin for Error Prone, we need to set the following flags in our build.gradle,

    "-Xep:NullAway:ERROR", // to activate NullAway
    "-XepOpt:NullAway:SerializeFixMetadata=true",
    "-XepOpt:NullAway:FixSerializationConfigPath=path_to_nullaway_config.xml",
    "-Xep:AnnotatorScanner:ERROR", // to activate Annotator AnnotatorScanner
    "-XepOpt:AnnotatorScanner:ConfigPath=path_to_scanner_config.xml",
    

    The following code snippet demonstrates how to configure the JavaCompile tasks in your build.gradle to use NullAway as a plugin for Error Prone:

     dependencies {  
     	annotationProcessor 'edu.ucr.cs.riple.annotator:annotator-scanner:1.3.6'  
     	annotationProcessor "com.uber.nullaway:nullaway:0.10.10"  
     	errorprone "com.google.errorprone:error_prone_core:2.4.0"  
     	errorproneJavac "com.google.errorprone:javac:9+181-r4173-1"  
     	//All other target project dependencies
     }  
       
       
     tasks.withType(JavaCompile) {  
     	// remove the if condition if you want to run NullAway on test code  
     	if (!name.toLowerCase().contains("test")) {  
     		options.errorprone {  
     		check("NullAway", CheckSeverity.ERROR)  
     		check("AnnotatorScanner", CheckSeverity.ERROR)  
     		option("NullAway:AnnotatedPackages", "org.example")  
     		option("NullAway:SerializeFixMetadata", "true")  
     		option("NullAway:FixSerializationConfigPath", "path_to/nullaway.xml")  
     		option("AnnotatorScanner:ConfigPath", "path_to/scanner.xml")  
     		}  
     		options.compilerArgs << "-Xmaxerrs"<< "100000"  
     		options.compilerArgs << "-Xmaxwarns" << "100000"  
     	}  
       
     }

    path_to_nullaway_config.xml and path_to_scanner_config.xml are configuration files that do not need to be created during the initial project setup. The script will generate these files, facilitating seamless communication between the script and the analysis. At this point, the target project is prepared for the Annotator to process.

    You must provide the Annotator with the paths to path_to_nullaway_config.xml and path_to_scanner_config.xml. Further details on this process are described in the sections below.

  • Running Annotator

    Annotator necessitates specific flag values for successful execution. You can provide these values through command line arguments.

    To run Annotator on the target project P, the arguments below must be passed to Annotator:

    Flag Description
    -bc,--build-command <arg> Command to run NullAway on target P enclosed in "". Please note that this command should be executable from any directory (e.g., "cd /Absolute/Path/To/P && ./build").
    -i,--initializer <arg> Fully qualified name of the @Initializer annotation.
    -d,--dir <arg> Directory where all outputs of AnnotatorScanner and NullAway are serialized.
    -cp, --config-paths Path to a TSV file containing values defined in Error Prone config paths given in the format: (path_to_nullaway_config.xml \t path_to_scanner_config).
    -cn, --checker-name Checker name to be used for the analysis. (use NULLAWAY to request inference for NullAway.)

By default, Annotator has the configuration below:

  1. When a tree of fixes is marked as useful, it only injects the root fix.
  2. Annotator will bailout from the search tree as soon as its effectiveness hits zero or less.
  3. Performs search to depth level 5.
  4. Uses javax.annotation.Nullable as @Nullable annotation.
  5. Cache reports results and uses them in the next cycles.
  6. Parallel processing of fixes is enabled.
  7. Downstream dependency analysis is disabled.

Here are some useful optional arguments that can alter the default configurations mentioned above:

Flag Description
-n,--nullable <arg> Sets custom @Nullable annotation.
-rboserr, --redirect-build-output-stderr Redirects build outputs to STD Err.

To learn more about all the optional arguments, please refer to OPTIONS.md

Here is a template command you can use to run Annotator from the CLI, using CLI options-

java -jar ./path/to/annotator-core.jar -d "/path/to/output/directory" -cp "/path/to/config/paths.tsv" -i com.example.Initializer -bc "cd /path/to/targetProject && ./gradlew build -x test"

To view descriptions of all flags, simply run the JAR with the --help option.

NullAway Compatibility

  • Annotator version 1.3.6 is compatible with NullAway version 0.10.10 and above.

License

Annotator is released under the MIT License. See the LICENSE file for more information.