-
Notifications
You must be signed in to change notification settings - Fork 778
Added RedundantNullCheck bug pattern #5121
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
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
cpovirk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for doing this, and I apologize again for not getting to it.
My suggested edits assume a few static imports:
import static com.google.errorprone.util.ASTHelpers.hasImplicitType;
import static com.google.errorprone.util.ASTHelpers.isConsideredFinal;
import static javax.lang.model.type.TypeKind.TYPEVAR;Whatever special cases you're up for adding tests for are great, but I can't complain when you already have a bunch of tests and I've left you hanging so long.
core/src/main/java/com/google/errorprone/bugpatterns/nullness/RedundantNullCheck.java
Show resolved
Hide resolved
core/src/main/java/com/google/errorprone/bugpatterns/nullness/RedundantNullCheck.java
Show resolved
Hide resolved
core/src/main/java/com/google/errorprone/bugpatterns/nullness/RedundantNullCheck.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/google/errorprone/bugpatterns/nullness/RedundantNullCheck.java
Show resolved
Hide resolved
| return buildDescription(tree).build(); | ||
| } | ||
|
|
||
| private static boolean isEffectivelyNullable(MethodSymbol methodSymbol, VisitorState state) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A totally optional thing that you could look into is handling of constructors: I think those end up in the MethodSymbol code path, and of course any call to a constructor produces a non-null value, regardless of annotations. Given that, you might well be able to return false if methodSymbol.isConstructor().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, done. That reminded me of literal initializers, which I now also covered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hadn't considered how much more we could do here if we wanted. (Even without getting into true "dataflow," we could cover many of the basics that we have in NullnessPropagationTransfer.) The cases you've already covered strike me as the most important ones: It's easier to imagine that someone might wrongly conclude that foo() can return null than that i + 1 can produce null :) The next steps that come to mind are to cover expressions that "pass through" values, like the ternary operator or a cast. I would actively recommend not trying to squeeze that into this PR, since I fear that I will disappear again if I force enough rounds of review. But that's something else you could consider as a follow-up change or in your internal version if it feels valuable enough.
…ck`](google/error-prone#5121). Also, use that check as motivation to finally make `AbstractBiMap` more understandable to nullness checkers. While in the area, I also made `inverse` be `private`. That required changing a reference in a subclass in the file to use `super.inverse`. As I understand it from my experience in 856123e, a `private` field like `inverse` is [accessible](https://docs.oracle.com/javase/specs/jls/se24/html/jls-6.html#jls-6.6.1) but not inherited ([1](https://docs.oracle.com/javase/specs/jls/se24/html/jls-8.html#d5e13771), [2](https://docs.oracle.com/javase/specs/jls/se24/html/jls-8.html#jls-8.2)). (And marginally simplify a couple `AbstractBiMap` subclasses by removing unnecessary type arguments.) RELNOTES=n/a PiperOrigin-RevId: 809750887
…ck`](google/error-prone#5121). Also, use that check as motivation to finally make `AbstractBiMap` more understandable to nullness checkers. While in the area, I also made `inverse` be `private`. That required changing a reference in a subclass in the file to use `super.inverse`. As I understand it from my experience in 856123e, a `private` field like `inverse` is [accessible](https://docs.oracle.com/javase/specs/jls/se24/html/jls-6.html#jls-6.6.1) but not inherited ([1](https://docs.oracle.com/javase/specs/jls/se24/html/jls-8.html#d5e13771), [2](https://docs.oracle.com/javase/specs/jls/se24/html/jls-8.html#jls-8.2)). (And marginally simplify a couple `AbstractBiMap` subclasses by removing unnecessary type arguments.) RELNOTES=n/a PiperOrigin-RevId: 810029934
|
@cpovirk Great, thank you, I incorporated and pushed your suggestions (with two additions - implicit vars which aren't parameters and literal initializers). Also added tests for the new use cases. |
|
@cpovirk Also we think that extending this with support for redundant |
That is a good question. I tend to have different views for different code: In places like Guava, we actively want to make sure that we produce NPE immediately if users pass For more typical code, I don't usually bother with defensive checks at all, since the damage from getting things "wrong" is more contained. There, I'd expect to see I would say that it's fine for you to do whatever suits you, but I will eventually end up running this by someone for internal review, so we may end up changing course then. We also always have the option of making the behavior configurable with a flag. That still leaves the question of which behavior to make the default, but it at least offers flexibility to users who have strong enough opinions. |
cpovirk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again. I could approve this as it is now to move the import process along, but I'll hold off in case you want to try to add requireNonNull support or make other changes. In the meantime, I'll retest what you have on our depot to see if anything new (or anything that I Just missed) jumps out at me.
| import javax.lang.model.element.ElementKind; | ||
|
|
||
| @BugPattern( | ||
| summary = "Explicit null check on a variable or method call that is not @Nullable within a @NullMarked scope.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got to wondering if someone might read the current text as saying that the null check has to be within a @NullMarked scope, as opposed to saying that the field or method needs to be so. Maybe we can avoid that by focusing on what we know instead of how we know it, as in the following...?
| summary = "Explicit null check on a variable or method call that is not @Nullable within a @NullMarked scope.", | |
| summary = "Explicit null check on an expression that is known not to be null.", |
That said, when I tested this out on our codebase, I saw a number of cases in which the value could have been null but the annotations were wrong :) So I'm tempted to say something like:
| summary = "Explicit null check on a variable or method call that is not @Nullable within a @NullMarked scope.", | |
| summary = "Explicit null check on an expression that is non-null according to nullness annotations.", |
But then that's not true for cases like literals and constructors... :)
Maybe this is ultimately best covered in the Markdown docs. But if you have more thoughts on how to phrase things here, maybe we can figure something out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, we should improve the wording here. Maybe just explicitly mention both (language semantics and annotations), something like:
Null check on an expression that is statically determined to be non-null according to language semantics or nullness annotations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe simply should instead of statically determined (which is redundant phrase in the context of static analysis itself):
Null check on an expression that should be non-null according to language semantics or nullness annotations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I like your "statically determined" phrasing over "should": Even when we qualify it with "according to language semantics or nullness annotations," I worry that "should" could be misread as somehow related to what the code does if it encounters null. I'm not sure I'm phrasing that well :\ Like, if I write...
if (next == null) {
throw new IllegalStateException();
}...then clearly next "should" be non-null. And that's "should" in a different sense than the one we'd be talking about here.
I agree that "statically determined" is technically redundant, but I think it's worth it for the emphasis. I say that especially because I'm seeing a lot of cases in which the actual problem is that a type is missing a @Nullable annotation :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed; then for now I changed it (both in pattern summary and in md) to:
Null check on an expression that is statically determined to be non-null according to language semantics or nullness annotations.
Let's think and improve more if needed.
| @@ -0,0 +1,52 @@ | |||
| A null check is redundant if it is performed on a variable or method call that is known to be non-null within a `@NullMarked` scope. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning to what I was saying about above how the nullness information we have may or may not come from annotations and how those annotations may or may not be correct....
I'm not sure if we have a consistent style for how we phrase these, but here are some things I might say:
Some code checks for
nullwhennullis "impossible." In some cases,nullis truly impossible according to the rules of the Java language, such as when testing the result of a constructor call. In other cases,nullis impossible according to nullness annotations on a parameter or method that is being used: Either the type is annotated with@NonNull, or it has no annotation but appears in the scope of a@NullMarkedannotation, which makes most unannotated types non-null.An "impossible" null check may indicate that the null check is a bug. For example, maybe it should instead be checking whether the value is an empty collection or
Optional. Alternatively, the check might merely have become unnecessary. Or, in some cases, the value can benullat runtime, and the real problem is that a method or parameter is missing a@Nullableannotation.
| return NO_MATCH; | ||
| } | ||
|
|
||
| if ((varSymbol.getKind() == ElementKind.LOCAL_VARIABLE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is another case in which I'm going to ask something more a question for the future: Now that we restrict checking to final (and effectively final) VarSymbol declaration, would it make sense to also cover fields?
(This also reminds me that exception parameters are always non-null, at least if no one assigns back to them! But I say that mostly just to get it out of my system.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other thoughts on this proposal (in addition to the ones above on line 61), again with the motivation of capturing them for the future:
If we cover fields, we could identify additional opportunities (while also keeping all the current ones) by looking for @NonNull annotations on them and @NullMarked annotations on the enclosing elements. That would let us treat even more fields as non-null, including but not limited to non-final fields.
That said, we do sometimes see users who claim that a field is non-null when it's merely non-null after some initialization occurs. So it's possible that those users would be annoyed if we were to complain that their lazy-initialization code checks for the nullness of a "non-null" field. That, at least, is an issue only for non-final fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Err, sorry, you do cover fields; I'm just focused on the branch this is not responsible for them.
I will be out for a couple hours, but I'll try to reevaluate when I get back. Several of my comments need revisions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, let's see if I can evaluate the full logic instead of over-focusing on specific details and losing the bigger picture.
There are a few VarSymbol cases in which we could report that a value is never null:
- for a field that matches either of the following conditions:
@NonNull(I'm not sure we check at the moment. That's fine)- a non-type-variable-typed, non-
@Nullablefield in the scope of@NullMarked
- for a parameter that matches either of the "field" conditions and is not an implicitly typed lambda parameter
- for a variable that matches both of the following conditions:
- "considered
final" - initialized to a
!isEffectivelyNullablemethod or to a literal (other thannull)
- "considered
- (probably also for a field that matches all the "variable" conditions, though that's another thing we don't check at the moment, and that's fine, too)
We should figure out whether that is even right. Whatever we settle on, and whatever part of that we implement now, it might be easiest to express with an isEffectivelyNullable(VarSymbol, ...) method. That would let us have a separate section for each case, from which we can return early, since we can't really do that with the logic inline in matchBinary. (If we have a separate method, that could also help with other possible future generalizations that we've discussed.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, all points are correct.
for a field that matches either of the following conditions:
@NonNull(I'm not sure we check at the moment. That's fine)
Good catch. I was focused mostly on @NullMarked scope (where @NonNull is redundant, at least when it comes to tools implementing null-marked spec), but since I already added support for it for methods out of @NullMarked scope, I changed it now for var symbols too.
probably also for a field that matches all the "variable" conditions
We have to think more when it comes to fields in some situations, including alignment with other tools, e.g. a field that is not marked as nullable in @NullMarked scope will not be considered nullable even if it is not considered final or is initialized with an isEffectivelyNullable method, so we'd need to take care of it during implementation. Hence I left the logic for fields to be annotation-driven only for now.
isEffectivelyNullable(VarSymbol, ...)
Good idea, I already did it for methods and the flow is easier to follow for them with that. Now I refactored the logic for var symbols that way too. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another aspect about fields is that they are more affected by build-time or runtime code manipulation tools (i.e. like Lombok, dependency injection, bytecode weaving in runtime, etc) than local variables I guess, so the static analysis assumptions we make might not be suitable to all scenarios in practice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that Error Prone conveniently blows up on Lombok code, at least :) But, yes, good point: Fields do get weird treatment from tools in a variety of situations. I also expect for humans to be less reliable at annotating their fields than their APIs simply because I expect them to focus on public members, while fields are usually private (aside from the occasional constants).
(I have some other distractions going on at the moment but should be able to get back to this tomorrow if I don't manage to today.)
cpovirk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, the results looked great, but I ended up with more thoughts anyway. I hope that it's just simplifications at this point.
| if (NullnessUtils.isAlreadyAnnotatedNullable(varSymbol)) { | ||
| return NO_MATCH; | ||
| } | ||
| if (varSymbol.asType().getKind() == TYPEVAR) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I was just looking at this suggestion of mine again, and I think I was mistaken: We shouldn't need to check TYPEVAR for variables: I'm thinking that the only case in which we could ever produce a finding for a type-variable-typed variable is a call to a method that returns @NonNull T for some type-variable type T. In that case, we're already relying on isEffectivelyNullable(MethodSymbol, ...) to do the right thing for TYPEVAR.
So I think we can remove the TYPEVAR check here, leaving only the one below (the one for methods).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, sorry again: I am mixed up, as I'll cover below. [edit: here]
| return NO_MATCH; | ||
| } | ||
|
|
||
| if ((varSymbol.getKind() == ElementKind.LOCAL_VARIABLE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other thoughts on this proposal (in addition to the ones above on line 61), again with the motivation of capturing them for the future:
If we cover fields, we could identify additional opportunities (while also keeping all the current ones) by looking for @NonNull annotations on them and @NullMarked annotations on the enclosing elements. That would let us treat even more fields as non-null, including but not limited to non-final fields.
That said, we do sometimes see users who claim that a field is non-null when it's merely non-null after some initialization occurs. So it's possible that those users would be annoyed if we were to complain that their lazy-initialization code checks for the nullness of a "non-null" field. That, at least, is an issue only for non-final fields.
core/src/main/java/com/google/errorprone/bugpatterns/nullness/RedundantNullCheck.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/google/errorprone/bugpatterns/nullness/RedundantNullCheck.java
Outdated
Show resolved
Hide resolved
|
I also wanted to write down somewhere how this differs from https://errorprone.info/bugpattern/ImpossibleNullComparison, at least as I understand it:
The long-term plan should likely be to merge these, probably offering configuration options and enhanced docs. But I'm hoping to be able to make the case that we shouldn't block on that. |
Makes sense. I pushed it now too, it is optional and disabled by default (can be enabled with |
- VarSymbol that is @nonnull outside @NullMarked scope is considered as non-null - Improved wording in the documentation
cpovirk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The results from running this over our depot look excellent—so many people who think that methods might return null when they actually throw an exception, for example, plus all the cases of missing @Nullable annotations that I'd mentioned before. I'm still not sure if we'll be able to justify turning it on for everyone, since some people have chosen to perform manual defensive checks on parameters, but I hope we can get there when we're finally able to focus more on nullness again!
I have run out of behavioral suggestions, so all that's left is some style-type things.
| VarSymbol varSymbol = nullCheck.varSymbolButUsuallyPreferBareIdentifier(); | ||
| if (varSymbol != null) { | ||
| isRedundant = !isEffectivelyNullable(varSymbol, state); | ||
| } else { | ||
| MethodSymbol methodSymbol = nullCheck.methodSymbol(); | ||
| if (methodSymbol != null) { | ||
| isRedundant = !isEffectivelyNullable(methodSymbol, state); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| VarSymbol varSymbol = nullCheck.varSymbolButUsuallyPreferBareIdentifier(); | |
| if (varSymbol != null) { | |
| isRedundant = !isEffectivelyNullable(varSymbol, state); | |
| } else { | |
| MethodSymbol methodSymbol = nullCheck.methodSymbol(); | |
| if (methodSymbol != null) { | |
| isRedundant = !isEffectivelyNullable(methodSymbol, state); | |
| } | |
| } | |
| VarSymbol varSymbol = nullCheck.varSymbolButUsuallyPreferBareIdentifier(); | |
| MethodSymbol methodSymbol = nullCheck.methodSymbol(); | |
| boolean isRedundant = false; | |
| if (varSymbol != null) { | |
| isRedundant = !isEffectivelyNullable(varSymbol, state); | |
| } else if (methodSymbol != null) { | |
| isRedundant = !isEffectivelyNullable(methodSymbol, state); | |
| } |
It might be even better to go a little further and, instead of setting isRedundant, just return immediately, especially after the slightly simplification I'm suggesting to the return statement. You could merge the checks into lines like:
if (varSymbol != null && !isEffectivelyNullable(varSymbol, state) {
return describeMatch(tree);
}| if (!OBJECTS_REQUIRE_NON_NULL.matches(tree, state)) { | ||
| return NO_MATCH; | ||
| } | ||
| if (tree.getArguments().isEmpty()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect we could safely ignore this possibility, since the code shouldn't compile and thus shouldn't ever get to Error Prone.
| return true; | ||
| } | ||
|
|
||
| if (isLocalOrResourceVariable && varDecl != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (isLocalOrResourceVariable && varDecl != null) { | |
| if (isLocalOrResourceVariable) { |
If varDecl is null, then we would have already thrown NPE during the call to hasImplicitType. We could move the check up, but my hope would be that a local/resource variable would always have a declaration, so I'm proposing to just remove it.
(If we did want to handle the null case, then we'd probably want to do something different so that we don't just fall through to the isInNullMarkedScope check at the end of the method.)
| } | ||
|
|
||
| if (isRedundant) { | ||
| return buildDescription(tree).build(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| return buildDescription(tree).build(); | |
| return describeMatch(tree); |
| if (initializer.getKind() == Tree.Kind.METHOD_INVOCATION) { | ||
| MethodInvocationTree methodInvocation = (MethodInvocationTree) initializer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (initializer.getKind() == Tree.Kind.METHOD_INVOCATION) { | |
| MethodInvocationTree methodInvocation = (MethodInvocationTree) initializer; | |
| if (initializer instanceof MethodInvocationTree methodInvocation) { |
| if (symbol instanceof VarSymbol) { | ||
| isRedundant = !isEffectivelyNullable((VarSymbol) symbol, state); | ||
| } else if (symbol instanceof MethodSymbol) { | ||
| isRedundant = !isEffectivelyNullable((MethodSymbol) symbol, state); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (symbol instanceof VarSymbol) { | |
| isRedundant = !isEffectivelyNullable((VarSymbol) symbol, state); | |
| } else if (symbol instanceof MethodSymbol) { | |
| isRedundant = !isEffectivelyNullable((MethodSymbol) symbol, state); | |
| } | |
| if (symbol instanceof VarSymbol varSymbol) { | |
| isRedundant = !isEffectivelyNullable(varSymbol, state); | |
| } else if (symbol instanceof MethodSymbol methodSymbol) { | |
| isRedundant = !isEffectivelyNullable(methodSymbol, state); | |
| } |
| private final boolean checkRequireNonNull; | ||
|
|
||
| @Inject | ||
| public RedundantNullCheck(ErrorProneFlags flags) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| public RedundantNullCheck(ErrorProneFlags flags) { | |
| RedundantNullCheck(ErrorProneFlags flags) { |
Just to follow our internal style of reducing the visibility of constructors that are going to be called only through reflection.
| } | ||
|
|
||
| if (isRedundant) { | ||
| return buildDescription(tree).build(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| return buildDescription(tree).build(); | |
| return describeMatch(tree); |
Great, I have pushed the style-type changes you suggested, all valid points, thanks! |
cpovirk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, let's see if my approval accomplishes anything in this repo. I can also keep an eye out for the internal review and see what I can do there.
Most of these came to my attention during the testing of [RedundantNullCheck](google/error-prone#5121). Notes: - For `BufferedInputStream` and `FilterInputStream`, see #128. - For `URL`, the docs for `getAuthority()` don't mention `null`, but it's easy enough to demonstrate that `null` is a possible return value: ```java var u = new URL("mailto:foo@bar.com"); System.out.println("authority " + u.getAuthority()); ``` - `AlgorithmParameters.toString()` is gross but documented as such, as eamonnmcmanus points out. - `Matcher.group()` is unfortunate: At one point, it could not return `null`, but that changed, as documented since [JDK-8274663](https://bugs.openjdk.org/browse/JDK-8274663). The change is a consequence of [`usePattern`](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/regex/Matcher.html#usePattern(java.util.regex.Pattern)), which was added all the way back in Java 5. I am a little tempted to lie here and leave the type as non-null. - In `Component`, I also removed an annotation on the package-private helper method used by `getName()`. - In `ResultSet`, `getBlob` and `getObject` have no documentatino about `null` returns. Gemini [wrote me some code that demonstrates the possible null returns](https://g.co/gemini/share/776a7669ae47). - Also in `ResultSet`, I simplified some type-parameter declarations. This ensures that the `Class<T>` parameter has its type argument in bounds, and it makes no difference to the result type, which is always nullable, regardless of the type argument. Fixes #128
Closes #5107. A null check is redundant if it is performed on a variable or method call that is known to be non-null within a `@NullMarked` scope. Within a `@NullMarked` scope, types are non-null by default unless explicitly annotated with `@Nullable`. Therefore, checking a variable or method return value (that isn't `@Nullable`) for nullness is unnecessary, as it should never be null. Example: ```java import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @NullMarked class MyClass { void process(String definitelyNonNull) { // BUG: Diagnostic contains: RedundantNullCheck if (definitelyNonNull == null) { System.out.println("This will never happen"); } // ... } String getString() { return "hello"; } @nullable String getNullableString() { return null; } void anotherMethod() { String s = getString(); // BUG: Diagnostic contains: RedundantNullCheck if (s == null) { // s is known to be non-null because getString() is not @nullable // and we are in a @NullMarked scope. System.out.println("Redundant check"); } String nullableStr = getNullableString(); if (nullableStr == null) { // This check is NOT redundant System.out.println("Nullable string might be null"); } } } ``` This check helps to clean up code and reduce clutter by removing unnecessary null checks, making the code easier to read and maintain. It also reinforces the contract provided by `@NullMarked` and `@Nullable` annotations. Fixes #5121 FUTURE_COPYBARA_INTEGRATE_REVIEW=#5121 from bdragan:5107-redundant_null_check f935970 PiperOrigin-RevId: 811786926
Add some missing `@Nullable` annotations. Most of these came to my attention during the testing of [RedundantNullCheck](google/error-prone#5121). Notes: - For `BufferedInputStream` and `FilterInputStream`, see #128. - For `URL`, the docs for `getAuthority()` don't mention `null`, but it's easy enough to demonstrate that `null` is a possible return value: ```java var u = new URL("mailto:foo@bar.com"); System.out.println("authority " + u.getAuthority()); ``` - `AlgorithmParameters.toString()` is gross but documented as such, as eamonnmcmanus points out. - `Matcher.group()` is unfortunate: At one point, it could not return `null`, but that changed, as documented since [JDK-8274663](https://bugs.openjdk.org/browse/JDK-8274663). The change is a consequence of [`usePattern`](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/regex/Matcher.html#usePattern(java.util.regex.Pattern)), which was added all the way back in Java 5. I am a little tempted to lie here and leave the type as non-null. - In `Component`, I also removed an annotation on the package-private helper method used by `getName()`. - In `ResultSet`, `getBlob` and `getObject` have no documentation about `null` returns. Gemini [wrote me some code that demonstrates the possible null returns](https://g.co/gemini/share/776a7669ae47). - Also in `ResultSet`, I simplified some type-parameter declarations. This ensures that the `Class<T>` parameter has its type argument in bounds, and it makes no difference to the result type, which is always nullable, regardless of the type argument. Fixes #128
…llCheck`](https://errorprone.info/bugpattern/RedundantNullCheck). `RedundantNullCheck` was from google/error-prone#5121. I had already taken some of its suggestions in 125fd02, but I'd been on the fence about this one. What pushed me over the edge was that my 34f3253 causes failures in nullness checking. I'm sure I could find another way to resolve those, but this approach probably makes more sense, anyway: If we imagine that nullness is enforced as in Kotlin (or possibly someday Java), then the call to `checkEntryNotNull` would immediately throw a `NullPointerException`, possibly with no message, instead of allowing the method to execute far enough to produce our custom `NullPointerException`, which includes both the key and the value. (I think there were a handful of other parameters like this one on which I also had declined to add `@Nullable`. I'm not revisiting that right now, but we could someday.) RELNOTES=n/a PiperOrigin-RevId: 830554169
…dundantNullCheck`](https://errorprone.info/bugpattern/RedundantNullCheck). `RedundantNullCheck` was from google/error-prone#5121. I had already taken some of its suggestions in 125fd02, but I'd been on the fence about these ones. What pushed me over the edge was that my 34f3253 causes failures in nullness checking. I'm sure I could find another way to resolve those, but this approach probably makes more sense, anyway: If we imagine that nullness is enforced as in Kotlin (or possibly someday Java), then the call to `checkEntryNotNull` would immediately throw a `NullPointerException`, possibly with no message, instead of allowing the method to execute far enough to produce our custom `NullPointerException`, which includes both the key and the value. (I think there were a handful of other parameters like this one on which I also had declined to add `@Nullable`. I'm not revisiting that right now, but we could someday.) RELNOTES=n/a PiperOrigin-RevId: 830554169
…dundantNullCheck`](https://errorprone.info/bugpattern/RedundantNullCheck). `RedundantNullCheck` was from google/error-prone#5121. I had already taken some of its suggestions in 125fd02, but I'd been on the fence about these ones. What pushed me over the edge was that my 34f3253 causes failures in nullness checking. I'm sure I could find another way to resolve those, but this approach probably makes more sense, anyway: If we imagine that nullness is enforced as in Kotlin (or possibly someday Java), then the call to `checkEntryNotNull` would immediately throw a `NullPointerException`, possibly with no message, instead of allowing the method to execute far enough to produce our custom `NullPointerException`, which includes both the key and the value. (I think there were a handful of other parameters like this one on which I also had declined to add `@Nullable`. I'm not revisiting that right now, but we could someday.) RELNOTES=n/a PiperOrigin-RevId: 830586416
Closes #5107.
A null check is redundant if it is performed on a variable or method call that is known to be non-null within a
@NullMarkedscope.Within a
@NullMarkedscope, types are non-null by default unless explicitly annotated with@Nullable.Therefore, checking a variable or method return value (that isn't
@Nullable) for nullness is unnecessary, as it should never be null.Example:
This check helps to clean up code and reduce clutter by removing unnecessary null checks, making the code easier to read and maintain.
It also reinforces the contract provided by
@NullMarkedand@Nullableannotations.