| # Null Checking |
| |
| 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] |
| |
| [NullAway]: https://github.com/uber/NullAway |
| [JSpecify]: https://jspecify.dev/docs/user-guide/ |
| [Error Prone]: https://errorprone.info/ |
| [runs as a static analysis step]: /build/android/docs/static_analysis.md#ErrorProne |
| |
| ## NullAway Configuration |
| |
| [Chromium's NullAway configuration] is as follows: |
| * [JSpecify mode] is enabled. |
| * `@Nullable` is `TYPE_USE`. |
| * Non-annotated means non-null for fields, parameters, and return types. |
| * Nullness of local variables is inferred. |
| * Copies of [supported annotations] exist under |
| `org.chromium.build.annotations`. |
| * These are a part of `//build/android:build_java`, which for convenience, |
| is a default dep of all `android_library` and `java_library` targets. |
| * Null checking is enabled only for classes annotated with `@NullMarked`. |
| * For other classes (e.g.: most library & OS APIs), `@Nullable` and |
| `@NonNull` are respected, but non-annotated types are permissive (return |
| types are non-null and parameters are nullable). |
| * Java collections and Guava's `Preconditions` are [modeled directly] in |
| NullAway. |
| * Some additional types are modeled via [`ChromeNullAwayLibraryModel`]. |
| * Android `onCreate()` methods are implicitly marked `@Initializer`. |
| * `assert foo != null` causes `foo` to no longer be nullable. |
| * [`assumeNonNull(foo)`] causes `foo` to no longer be nullable without actually |
| checking. |
| |
| [Chromium's NullAway configuration]: https://source.chromium.org/search?q=%22XepOpt:NullAway%22%20f:compile_java%20-f:third_party&sq=&ss=chromium |
| [JSpecify mode]: https://github.com/uber/NullAway/wiki/JSpecify-Support |
| [supported annotations]: https://github.com/uber/NullAway/wiki/Supported-Annotations |
| [`ChromeNullAwayLibraryModel`]: https://source.chromium.org/chromium/chromium/src/+/main:tools/android/errorprone_plugin/src/org/chromium/tools/errorprone/plugin/ChromeNullAwayLibraryModel.java |
| [modeled directly]: https://github.com/uber/NullAway/blob/HEAD/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java |
| [`assumeNonNull(foo)`]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/openscreen/src/build/android/java/src/org/chromium/build/NullUtil.java |
| |
| ## Nullness Migration |
| |
| We are actively opting classes into enforcement and hope to be complete by |
| March 2025. See details in [crbug.com/389129271]. |
| |
| [crbug.com/389129271]: https://crbug.com/389129271 |
| |
| ## Nullness Primer |
| |
| ### Type Annotations |
| |
| ```java |
| // 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; |
| ``` |
| |
| ### Method Annotations |
| |
| NullAway analyzes code on a per-method basis. These annotations tell it how |
| about pre/post conditions: |
| |
| ```java |
| // 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 isThingEnabled() { |
| 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 isParamNull(@Nullable String foo) { |
| return foo != null; |
| } |
| ``` |
| |
| ### Object Construction and Destruction |
| |
| **Construction:** |
| |
| * NullAway warns if any non-null fields are still nullable at the end of a |
| constructor. |
| * When a class uses two-phase initialization (e.g., has an `onCreate()` or |
| `initialize()`), you can tell NullAway to not check for null until after |
| methods annotated with `@Initializer` are called. |
| * `@Initializer` can also be used for `static` methods, which impacts |
| warnings for `static` fields. |
| |
| **Destruction:** |
| |
| For classes with `destroy()` methods that set fields to `null` that would |
| otherwise be non-null, you can either: |
| |
| 1) Annotate the fields as `@Nullable` and add `!isDestroyed()` asserts / guards |
| where necessary (where `isDestroyed()` is annotated with |
| `@EnsuresNonNullIf(value=..., result=false)`), or |
| 2) Annotate the `destroy()` method with `@SuppressWarnings("NullAway")`. |
| |
| ### JNI |
| |
| * Nullness is not checked for `@CalledByNative` methods ([crbug/389192501]). |
| * Nullness **is checked** via `assert` statements for Java->Native methods |
| (when `@NullMarked` exists). |
| |
| [crbug/389192501]: https://crbug.com/389192501 |
| |
| ## NullAway Shortcomings |
| |
| It does not understand when local variables contain non-null information. |
| * Feature request: https://github.com/uber/NullAway/issues/98 |
| |
| It does not infer nullness of inferred generics. |
| * Feature request: https://github.com/uber/NullAway/issues/1075 |
| |
| Validation of `@Contract` within methods that use it is broken. |
| * Bug: https://github.com/uber/NullAway/issues/1104 |
| |
| ## FAQ |
| |
| **Q: Why not use Checker Framework?** |
| |
| A: Chromium already uses Error Prone, so NullAway was easy to integrate. |
| |
| **Q: How do `@NullUnmarked` and `@SuppressWarnings("ErrorProne")` differ?** |
| |
| A: NullAway treats these two the same. In Chromium, `@SuppressWarnings` is used |
| when a warning is unavoidable (appeasing NullAway would make the code worse), |
| and `@NullUnmarked` is used when a method has not yet been updated to support |
| `@Nullable` annotations (its a remnant of our [automated adding of |
| annotations]). |
| |
| [automated adding of annotations]: https://docs.google.com/document/d/1KNKs7jI8uoBLfBq4HCuGTYPLuTOMgUEuKrzyCUFjEk8/edit?tab=t.0#heading=h.1y1pwesy8vhq |
| |
| **Q: Can I use JSpecify Annotations?** |
| |
| A: Yes. For code that will be mirrored and built in other environments, it is |
| best to use JSpecify annotations. You'll probably want to set: |
| |
| ```gn |
| 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 |
| ``` |