NullAwayAnnotator
or simply (Annotator) is a tool that can automatically infer nullability types in the given source code and injects the
corresponding annotations to pass NullAway checks.
Applying NullAway to build systems will require manual effort of applying annotations to source code. Even if a code is free of nullability errors, it is still required to annotate the code 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 applying NullAway to 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 leaving no remaining errors.
In the code below, NullAway
will report 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 injects the corresponding annotations. For unresolved errors, suppression annotations are injected.
The output below is 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 effects of a change through the entire module and injects several followups annotations to fully resolve one specific warning.
Annotator is also capable of processing modules within monorepos considering modules public APIs and the impacts of annotations on downstream dependencies for better results.
No installation is required, annotator jar file is provided via maven central repository. Find the artifact id below:
GROUP: edu.ucr.cs.riple.annotator
ID: annotator-core
ID: annotator-scanner
Annotator
version1.3.6
is compatible withNullAway
version0.10.10
and above.
This sections describes how to run Annotator
on any project.
Below are the instructions to prepare the target project:
NullAway
checker must be activated with version >=0.10.10
AnnotatorScanner
checker must be activated with version >=1.3.6
, see more aboutAnnotatorScanner
here.
"-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",
path_to_nullaway_config.xml
and path_to_scanner_config.xml
are config files which are not necessary to create at the time of preparing the project.
These two files will be created by the script and enables the communication between the script and the analysis.
Please find a sample project setup below:
dependencies {
annotationProcessor "edu.ucr.cs.riple:nullaway:0.9.6"
annotationProcessor "edu.ucr.cs.riple.annotator:annotator-scanner:1.3.5"
compileOnly "com.google.code.findbugs:jsr305:3.0.2"
errorprone "com.google.errorprone:error_prone_core:2.3.2"
errorproneJavac "com.google.errorprone:javac:9+181-r4173-1"
}
tasks.withType(JavaCompile) {
if (!name.toLowerCase().contains("test")) {
options.errorprone.errorproneArgs += ["-XepDisableAllChecks",
"-Xep:NullAway:ERROR",
"-XepOpt:NullAway:AnnotatedPackages=com.uber",
"-XepOpt:NullAway:SerializeFixMetadata=true",
"-XepOpt:NullAway:FixSerializationConfigPath=/tmp/NullAwayFix/config.xml",
"-Xep:AnnotatorScanner:ERROR",
"-XepOpt:AnnotatorScanner:ConfigPath=/tmp/NullAwayFix/scanner.xml",
]
}
}
At this moment, the target project is ready for the Annotator to process.
We need to inform the Annotator
about path_to_nullaway_config.xml
and path_to_scanner_config.xml
(number 2 and 3 in the next section), please read the section below.
Annotator
requires certain flag values to be able to run successfully. We can pass these values via command line arguments or a config file, we will describe each approach in next sections.
In order to run Annotator
on target project P
, arguments below must be passed to Annotator
:
-bc,--build-command <arg>
: Command to runNullAway
on targetP
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 ofAnnotatorScanner|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
)
By default, Annotator
has the configuration below:
- When a tree of fixes is marked as useful, it only injects the root fix.
- Annotator will bailout from the search tree as soon as its effectiveness hits zero or less.
- Performs search to depth level
5
. - Uses
javax.annotation.Nullable
as@Nullable
annotation. - Cache reports results and uses them in the next cycles.
- Parallel processing of fixes is enabled.
- Downstream dependency analysis is disabled.
Here are optional arguments which can alter default configurations above:
-ch,--chain
: Injects the complete tree of fixes associated to the fix.-db,--disable-bailout
: Annotator will not bailout from the search tree as soon as its effectiveness hits zero or less and completely traverses the tree until no new fix is suggested.-depth,--depth <arg>
: Sets the depth of the analysis search.-n,--nullable <arg>
: Sets custom@Nullable
annotation.-dc,--disable-cache
: Disables cache usage.-dpp,--disable-parallel-processing
: Disables parallel processing of fixes within an iteration.-rboserr, --redirect-build-output-stderr
: Redirects Build outputs toSTD Err
.-exs, --exhaustive-search
: Annotator will perform an exhaustive search which it injects@Nullable
on all elements involved in an error regardless of their overall effectiveness. (This feature is used mostly in experiments and may not have a practical use.)-dol
,--disbale-outer-loop
: Disables Outer Loop (This feature is used mostly in experiments and may not have a practical use.)-adda
,--activate-downstream-dependencies-analysis
: Activates downstream dependency analysis.-ddbc
,--downstream-dependencies-build-command <arg>
: Command to build all downstream dependencies at once, this command must include changing directory from root to the target project.-nlmlp
,--nullaway-library-model-loader-path <arg>
: NullAway Library Model loader path.-fr
,--force-resolve <arg>
: Forces remaining unresolved errors to be silenced using suppression annotations. Fully qualified annotation name for@NullUnmarked
must be passed.-am
,--analysis-mode <arg>
: Analysis mode. Can be [default|upper_bound|lower_bound|strict]-di
,--deactivate-infere
: Disabled inference of@Nullable
annotation.-drdl
,--deactivate-region-detection-lombok
: Deactivates region detection for Lombok.-nna
,--nonnull-annotations <arg>
: Adds a list of nonnull annotations separated by comma to be acknowledged by Annotator (e.g. com.example1.Nonnull,com.example2.Nonnull)eic
,enable-impact-cache
: Enables fixes impacts caching for next cycles.
In this approach we will initialize all flag values in one single file, and pass the path to the Annotator
.
See the format of the config file below with sample values:
{
"BUILD_COMMAND": "cd /path/to/target && command to run javac with analysis (e.g. ./gradlew :p:compileJava)",
"ANNOTATION": {
"INITIALIZER": "com.example.Initializer",
"NULLABLE": "javax.annotation.Nullable",
"NULL_UNMARKED": "com.example.NullUnmarked",
"NONNULL": [
"com.example.Nonnull"
]
},
"OUTPUT_DIR": "/tmp/NullAwayFix",
"CHAIN": false,
"PARALLEL_PROCESSING": true,
"CACHE_IMPACT_ACTIVATION": false,
"CACHE": true,
"BAILOUT": true,
"REDIRECT_BUILD_OUTPUT_TO_STDERR": false,
"EXHAUSTIVE_SEARCH": false,
"OUTER_LOOP": true,
"CONFIG_PATHS": [
{
"NULLAWAY": "path_to_nullaway_config.xml",
"SCANNER": "path_to_scanner_config.xml"
}
],
"PROCESSORS": {
"LOMBOK": true
},
"DEPTH": 1,
"DOWNSTREAM_DEPENDENCY": {
"ACTIVATION":true,
"BUILD_COMMAND": "cd /path/to/dependencies && command to run javac with analysis (e.g. ./gradlew :p:compileJava)",
"LIBRARY_MODEL_LOADER_PATH": "path to nullaway library model loader jar",
"ANALYSIS_MODE": "default"
},
"FORCE_RESOLVE": false,
"INFERENCE_ACTIVATION": true
}
Below is the description of each setting:
BUILD_COMMAND
: The command to executeNullAway
for the project. (This command must include changing directory to target project from root)INITIALIZER
: Fully qualified name of theInitializer
annotation to inject on detected initializer methods.NULLABLE
: Fully qualified name of theNullable
annotation.OUTPUT_DIR
: Directory where the serialized output of NullAway should be written.CONFIG_PATHS
: Array of JSON objects where each contains the path to nullaway and scanner Error Prone checker. The first element in this array will be considered as the target module and rest as downstream dependencies.PARALLEL_PROCESSING
: Enables parallel processing of fixes within an iteration.CACHE_IMPACT_ACTIVATION
: Enables fixes impacts caching for next cycles.CACHE
: if set totrue
, cache usage will be enabled.BAILOUT
: if set totrue
, Annotator will bailout from the search tree as soon as its effectiveness hits zero or less, otherwise it will completely travers the tree until no new fix is suggestedDEPTH
: The depth of the analysis.REDIRECT_BUILD_OUTPUT_TO_STDERR
: If true, build output will be redirected toSTD Error
.EXHAUSTIVE_SEARCH
: If true, the exhaustive search will be activated.OUTER_LOOP
: If true, the outer loop will be enabled.DOWNSTREAM_DEPENDENCY:ACTIVATION
: Controls downstream dependency feature activation.DOWNSTREAM_DEPENDENCY:BUILD_COMMAND
: Single command to build all downstream dependencies.DOWNSTREAM_DEPENDENCY:LIBRARY_MODEL_LOADER_PATH
: Path to NullAway library models loader.DOWNSTREAM_DEPENDENCY:ANALYSIS_MODE
: Analysis mode for downstream dependencies. Can be [default|upper_bound|lower_bound|strict]FORCE_RESOLVE
: If true, remaining unresolved errors will be silenced using suppression annotations.INFERENCE_ACTIVATION
: If true, inference of@Nullable
annotation will be activated.PROCESSORS:LOMBOK:ACTIVATION
: If true, potentially impacted regions will be extended with generated code by Lombok.
Pass the path to the config file above with -p,--path
argument to core.jar
and no other flag is required.
To see all flags description simply run the jar with --help
.