styleguide/java/nullaway.md
Chromium uses NullAway to enforce JSpecify-style @Nullable annotations.
NullAway is a Error Prone plugin and runs as a static analysis step for
targets without chromium_code = false.
[TOC]
Chromium's NullAway configuration is as follows:
@Nullable is TYPE_USE.@NonNull).org.chromium.build.annotations.
//build/android:build_java, which for convenience,
is a default dep of all android_library and java_library targets.@NullMarked.
@Nullable and
@NonNull are respected, but non-annotated types are permissive (return
types are non-null and parameters are nullable).Preconditions are modeled directly in
NullAway.
ChromeNullAwayLibraryModel.onCreate() (and similar) methods are implicitly marked @Initializer.We are actively opting classes into enforcement. Track progress via crbug.com/389129271.
// Plain Objects:
private String mNonNullString;
private @Nullable String mNullableString;
private Outer.@Nullable Inner mNullableNestedType;
// Arrays:
private String @Nullable[] mNullableArrayOfNonNullString;
private @Nullable String[] mNonNullArrayOfNullableString;
// Generics:
private List<@Nullable String> mNonNullListOfNullableString;
private @Nullable Callback<@Nullable String> mNullableCallbackOfNullableString;
// Does not compile (annotation must come immediately before type):
@Nullable
private String mInvalidAnnotation;
NullAway analyzes code on a per-method basis. These annotations tell it how about pre/post conditions:
// Using this with non-private methods never makes sense.
@RequiresNonNull("mNullableString")
private void usesNullableString() {
// No warning:
if (mNullableString.isEmpty()) { ... }
}
@EnsuresNonNull("mNullableString")
private void codeCanCallThisAndThenUseNullableString() {
// This will warn if mNullableString is @Nullable at any egress.
assert mNullableString != null;
}
// If this method returns true, then mThing is non-null.
@EnsuresNonNullIf("mThing")
private boolean isThingEnabled() {
return mThing != null;
}
// Also works with static fields and negated return values.
@EnsuresNonNullIf(value={"sThing1", "sThing2"}, result=false)
private static boolean isDestroyed() {
return sThing1 == null || sThing2 == null;
}
// If foo is null, this method returns false.
// Most other forms of contracts are not supported.
@Contract("null -> false")
private boolean isParamNonNull(@Nullable String foo) {
return foo != null;
}
// Returns null only when defaultValue is null
@Contract("_, !null -> !null")
@Nullable String getOrDefault(String key, @Nullable String defaultValue) {
return defaultValue;
}
// Starts as null, but may not be assigned a nullable value.
private @MonotonicNonNull String mSomeValue;
public void doThing(String value) {
// Emits a warning since mSomeValue is nullable:
helper(mSomeValue);
mSomeValue = value;
// No warning about mSomeValue being nullable, even though it's used in a lambda.
PostTask.postTask(TaskTraits.USER_BLOCKING, () -> helper(mSomeValue));
}
// Always use "import static" for assumeNonNull / assertNonNull.
import static org.chromium.build.NullUtil.assumeNonNull;
import static org.chromium.build.NullUtil.assertNonNull;
public String void example() {
// Prefer statements over expressions to keep preconditions separate from usage.
assumeNonNull(mNullableThing);
assert mOtherThing != null;
// It supports nested fields and getters.
assumeNonNull(someObj.nullableField);
assumeNonNull(someObj.getNullableThing());
// Use its expression form when it is more readable to do so.
someHelper(assumeNonNull(Foo.maybeCreate(true)));
// Use assertNonNull when you need an assert as an expression.
mNonNullField = assertNonNull(dict.get("key"));
String ret = obj.getNullableString();
if (willJustCrashLaterAnyways) {
// Use "assert" when not locally dereferencing the object.
assert ret != null;
} else {
// Use "requireNonNull()" when returning null might lead to bad things.
// Asserts are enabled only on Canary and are set as "dump without crashing".
Objects.requireNonNull(ret);
}
return ret;
}
// Use "assertNonNull(null)" for unreachable code.
public String describe(@MyIntDef int validity) {
return switch (validity) {
case MyIntDef.VALID -> "okay";
case MyIntDef.INVALID -> "not okay";
default -> assertNonNull(null);
};
}
Construction:
onCreate() or
initialize()), you can tell NullAway to pretend all such methods have
been called before performing validation.@Initializer can also be used for static methods, which impacts
warnings for static fields.@Initializer methods are actually called is not checked.*** note
Note: When multiple setters are always called after constructing an object,
prefer to create an single initialize() method that sets them instead.
Destruction:
For classes with destroy() methods that set fields to null that would
otherwise be non-null, you can either:
@Nullable and add !isDestroyed() asserts / guards
where necessary (where isDestroyed() is annotated with
@EnsuresNonNullIf(value=..., result=false)), ordestroy() method with @SuppressWarnings("NullAway").View Binders:
It might seem appropriate to mark onBindViewHolder() with @Initializer,
but these are not really "methods that are called immediately after the
constructor". Instead, consider adding an assertBound() method.
Example:
@EnsuresNonNull({"mField1", "mField2", ...})
private void assertBound() {
assert mField1 != null;
assert mField2 != null;
...
}
@CalledByNative methods (crbug/389192501).assert statements for Java->Native methods
(when @NullMarked exists).NullAway has no special handling for classes with public fields and will emit
a warning for any non-primitive non-@Nullable fields not initialized by a
constructor.
Fix this by:
Generate->Constructor function that will do this)./* paramName= */ comments
for the parameters.final.Some methods are technically @Nullable, but effectively @NonNull. That is,
they are marked as having @NonNull return types despite sometimes returning
null. Examples:
Activity.findViewById()Context.getSystemService()PreferenceManager.findPreference() (this one via ChromeNullAwayLibraryModel)Enforcing null checks for these would be detrimental to readability.
For Chromium-authored code that falls into this bucket, prefer to add companion "Checked" methods over mis-annotating nullability.
Example:
// When you're not sure if the tab exists:
public @Nullable Tab getTabById(String tabId) {
...
}
// When you know the tab exists:
public Tab getTabByIdChecked(String tabId) {
return assertNonNull(getTabById(key));
}
Supplier<T> to a method that accepts a Supplier<@Nullable T>,
you must explicitly cast it to the nullable form. See
https://github.com/uber/NullAway/issues/1356 for why this is.Supplier<@Nullable T> to a method that accepts a Supplier<T>,
use SupplierUtils.asNonNull().
Supplier<@Nullable T>, and add assumeNonNull() / assertNonNull()
to .get() calls accordingly.Does not work: boolean isNull = thing == null; if (!isNull) { ... }
Checking that @Contract is correct is buggy (we disable it). @Contract
still apply to callers though (they are assumed to be true).
Q: Why not use Checker Framework?
Q: How do @NullUnmarked and @SuppressWarnings("NullAway") differ?
@SuppressWarnings leaves the method signature @NullMarked.@NullUnmarked causes parameters and return types to have unknown
nullability, and thus also suppress nullness warnings that may exist at a
method's call sites.Q: Can I use JSpecify Annotations?
deps += [ "//third_party/android_deps:org_jspecify_jspecify_java" ]
# Prevent automatic dep on build_java.
chromium_code = false
# Do not let chromium_code = false disable Error Prone.
enable_errorprone = true