Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Use an annotation like @MonotonicNonNull for better reasoning about field nullability inside lambdas #1148

Closed
agrieve opened this issue Feb 19, 2025 · 9 comments · Fixed by #1149

Comments

@agrieve
Copy link

agrieve commented Feb 19, 2025

Similar to #570, but not stream-specific.

We've hit this scenario 3 or 4 times in chrome so far:

import org.jspecify.annotations.Nullable;
import org.jspecify.annotations.NullMarked;

@NullMarked
public class Foo {
  @Nullable Object mThing;

  void method(Object a, @Nullable Object b) {
    mThing = new Object();
    Runnable r = () -> mThing.hashCode();
    r.run();
  }
}
Foo.java:10: warning: [NullAway] dereferenced expression mThing is @Nullable
    Runnable r = () -> mThing.hashCode();
                             ^
    (see http://t.uber.com/nullaway )
  • When the field in question starts as null, but has only non-null assignments, then it is safe to assume it is non-null for dereferences that occur after an assignment (since it can never return to being null).
  • For lambdas that are constructed after an assignment, we should not warn about dereferences made by lambdas.
@msridhar
Copy link
Collaborator

When the field in question starts as null, but has only non-null assignments, then it is safe to assume it is non-null for dereferences that occur after an assignment (since it can never return to being null).

Within a single method, we already have this level of reasoning, and it could be extended to callee methods via @RequiresNonNull.

For lambdas that are constructed after an assignment, we should not warn about dereferences made by lambdas.

In the example the code is safe because of the r.run() call that happens in the same method. But in a case where run() is invoked later or asynchronously, it's not clear from a local analysis that mThing wasn't set to null again before the lambda executes. If there are only non-null assignments then even an asynchronous invocation is safe. But proving that requires a combination of global reasoning (only non-null assignments) and local reasoning (that the lambda is only created after such an assignment) that we don't do right now, and may not be straightforward to add.

Does the real code use a Runnable? Or is it something where it is more obvious that the lambda executes immediately and not asynchronously?

@agrieve
Copy link
Author

agrieve commented Feb 19, 2025

Here's the most recent example: https://chromium-review.googlesource.com/c/chromium/src/+/6264464/6/components/messages/android/java/src/org/chromium/components/messages/MessageWrapper.java#140

I think all of the real examples are for code that is run asynchronously, which I guess makes this different from streams.

I don't love it, but maybe could use a new annotation to remove the need for global reasoning. E.g. @OnceNullable.

I thought this one might be hard :P. It's certainly something we can live without.

@msridhar
Copy link
Collaborator

Thinking more this seems like it could be handled if we had better support for checking a @MonotonicNonNull annotation (like that from Checker Framework). If we have a @MonotonicNonNull field f, and we know locally within a method m that f is initialized before a lambda l is created, then we can analyze the body of l assuming f is @NonNull. That part wouldn't be hard at all. The only more substantial thing to implement would be checking that only @NonNull values are assigned to @MonotonicNonNull fields; and that might not be too hard either. So maybe adding this support is doable, assuming folks are willing to add @MonotonicNonNull annotations to the relevant fields.

@msridhar msridhar changed the title Feature Request: Detect when dereferences within lambdas Feature Request: Use an annotation like @MonotonicNonNull for better reasoning about field nullability inside lambdas Feb 20, 2025
@agrieve
Copy link
Author

agrieve commented Feb 20, 2025

Well that's a much better name than I came up with :P. I'm sure we'd use it if support were added.

msridhar added a commit that referenced this issue Feb 25, 2025
Fixes #1148 

We add explicit support for any annotation named `@MonotonicNonNull` and
add our own version of the annotation to our annotations package. The
main additional support is that we now reason that once assigned a
non-null value, `@MonotonicNull` fields remain non-null when accessed
from subsequent lambdas, even if the lambdas are invoked asynchronously.
@agrieve
Copy link
Author

agrieve commented Feb 26, 2025

Wow, great stuff! Looking forward to trying this out!

@msridhar
Copy link
Collaborator

We should have a new release out soon!

@msridhar
Copy link
Collaborator

@agrieve NullAway 0.12.4 is now released with this fix

aarongable pushed a commit to chromium/chromium that referenced this issue Mar 6, 2025
Includes fixes to warnings introduced by the change. Main fixes are:

uber/NullAway#1126
uber/NullAway#1139
uber/NullAway#1148
uber/NullAway#1151

No-try due to flaky optional windows bot.

No-Try: true
Bug: 389129271
Change-Id: Ia2abd8482ffabba7885b616048996f97da856b9a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6325293
Reviewed-by: Mohamed Heikal <mheikal@chromium.org>
Commit-Queue: Andrew Grieve <agrieve@chromium.org>
Owners-Override: Andrew Grieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1429012}
github-actions bot pushed a commit to kaidokert/chrome_base_mirror that referenced this issue Mar 7, 2025
Includes fixes to warnings introduced by the change. Main fixes are:

uber/NullAway#1126
uber/NullAway#1139
uber/NullAway#1148
uber/NullAway#1151

No-try due to flaky optional windows bot.

No-Try: true
Bug: 389129271
Change-Id: Ia2abd8482ffabba7885b616048996f97da856b9a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6325293
Reviewed-by: Mohamed Heikal <mheikal@chromium.org>
Commit-Queue: Andrew Grieve <agrieve@chromium.org>
Owners-Override: Andrew Grieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1429012}
NOKEYCHECK=True
GitOrigin-RevId: 4c17934359abc12d6cf5aef20a073285a25a2d50
@agrieve
Copy link
Author

agrieve commented Mar 7, 2025

Just tried this out. Working great for non-static fields, but it looks like it's treated the same as @Nullable for static fields.

aarongable pushed a commit to chromium/chromium that referenced this issue Mar 7, 2025
Support for this annotation was recently added to NullAway. It removes
some uses of assumeNonNull().

Note: It does not yet work with static fields. Reported here:
uber/NullAway#1148 (comment)

Bug: 389129271
Change-Id: I1c24769b48cb381ba6a5974b24d04f2c772d226d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6334458
Owners-Override: Andrew Grieve <agrieve@chromium.org>
Auto-Submit: Andrew Grieve <agrieve@chromium.org>
Reviewed-by: Henrique Nakashima <hnakashima@chromium.org>
Commit-Queue: Andrew Grieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1429766}
@msridhar
Copy link
Collaborator

msridhar commented Mar 8, 2025

@agrieve sorry I didn't test static fields. I think I know what needs to be done though. Can you open a separate issue to make it easier to track?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants