-
Notifications
You must be signed in to change notification settings - Fork 299
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
Support @EnsuresNonNullIf #1044
Conversation
717d7ca
to
60c09cd
Compare
Force pushing just so I can change the user name in the commit, which wasn't matching my github user. |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1044 +/- ##
============================================
- Coverage 87.54% 87.54% -0.01%
- Complexity 2138 2165 +27
============================================
Files 83 85 +2
Lines 7003 7114 +111
Branches 1367 1386 +19
============================================
+ Hits 6131 6228 +97
- Misses 451 458 +7
- Partials 421 428 +7 ☔ View full report in Codecov by Sentry. |
This looks very promising, thanks! I'll just answer the questions for now and assume a full code review should wait for later.
I think we can do a bit better without too much trouble. Looking at the code here, I think the
No problem. I think if needed, a first version could even skip that as long as we track multiple field support as an issue for a follow up.
Yes, I think that would be good for flexibility, and it shouldn't be too hard to implement.
👍 It can exist in the class itself or it can be inherited (see below).
We need to prevent cases like (rough syntax): class A {
@Nullable Object foo;
@EnsuresNonNullIf("this.foo")
boolean hasFoo() { return this.foo != null; }
}
class B extends A {
@Override
boolean hasFoo() { return true; }
}
...
void test() {
A a = new B();
if (a.hasFoo()) {
a.foo.toString(); // NPE!
}
} To prevent this, an overriding method should have at least as strong an |
@msridhar I believe I managed to leverage the However, as you can see in the ignored test I wrote, the engine doesn't get something like Other remarks:
|
@msridhar I added a few more features, like the post-condition check we spoke about and a The only thing missing is that if the if check is extracted to a boolean variable, the handler gets lost. See all tests that are disabled. Thoughts? |
@mauricioaniche will look more closely at everything soon, but briefly:
This is #98, NullAway doesn't support this yet. So don't worry about getting that working 🙂 |
@msridhar Oh, that's good to know, I thought I was making a mistake somewhere! Then, I believe I coded everything I wanted for this feature.
Looking forward to your code review. More importantly, maybe you have some extra test cases you'd like me to try? I'm sure your eyes are way more trained than mine to spot corner cases! |
@mauricioaniche will try to look at this one very soon. In the meantime, are you able to sign the CLA? |
CLA signed! |
protected boolean validateAnnotationSemantics( | ||
MethodTree tree, MethodAnalysisContext methodAnalysisContext) { | ||
// If no body in the method, return false right away | ||
// TODO: Do we need proper error message here? |
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.
See this TODO
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.
Would this happen for an abstract / interface method? If so, seems best to report some kind of error?
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 existing EnsuresNonNullHandler
actually returns true
in this case. I'm doing the same now, as someone probably thought deeper about it!
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.
Keeping this one open for you to close it yourself, if you agree!
...src/main/java/com/uber/nullaway/handlers/contract/fieldcontract/EnsuresNonNullIfHandler.java
Show resolved
Hide resolved
...src/main/java/com/uber/nullaway/handlers/contract/fieldcontract/EnsuresNonNullIfHandler.java
Outdated
Show resolved
Hide resolved
...src/main/java/com/uber/nullaway/handlers/contract/fieldcontract/EnsuresNonNullIfHandler.java
Outdated
Show resolved
Hide resolved
if (visitingAnnotatedMethod) { | ||
// We might have already found another return tree that results in what we need, | ||
// so we don't keep going deep. | ||
if (semanticsHold) { |
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.
Any thoughts about this? A method with multiple returns, can we be smart about it?
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.
See comment above about checking return
statements individually
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 for the contribution! It will take me some time to review this whole PR. I am pushing my comments in chunks when I get time, to get the feedback out faster.
annotations/src/main/java/com/uber/nullaway/annotations/EnsuresNonNullIf.java
Outdated
Show resolved
Hide resolved
nullaway/src/main/java/com/uber/nullaway/handlers/AbstractFieldContractHandler.java
Outdated
Show resolved
Hide resolved
@msridhar you were right about the store! I was under the assumption that always looking at the "thenStore" was the right thing to do, because IIUC, it represents the path that follows the return expression. However, I didn't expect that the engine would not really be 100% accurate around the "NULL". Using the "elseStore" allowed me to look at NON_NULL always, which seems to be precise. Why does that happen? Moreover: I put it back the return default to true and fixed the minor nits as well. Would you mind giving it a final check to see if everything looks fine? Before merging: I think it's best to squash the commits just so the history of the repository doesn't get full of "bad commits". Do you want me to squash or do you do it via github? |
@mauricioaniche thanks again for this amazing contribution! Will take one more look before merging. Don't worry we always squash and merge so the intermediate commits won't remain. |
I'd have to re-read the Checker dataflow manual to be really sure, but I think what goes on is that for every expression of type |
@msridhar wait a sec, I believe I found a case where it breaks |
Take a look at the test EDIT: I gotta go pick up my kid from school, will be back soon. |
@msridhar Done. It was a corner case on when return=false and the method returned a literal boolean. I had the feeling I had to also handle it. Main change is here. I also renamed the variable and improved the documentation around it. There was a wrong test case that was revealed after I fixed this bug! If you have the energy, please go through the test cases, and see if they are all correct!! Thank you! |
…, using null should be fine
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.
Found another potential issue, see below
" @EnsuresNonNullIf(value=\"nullableItem\", result=true)", | ||
" public boolean hasNullableItem() {", | ||
" if(nullableItem != null) {", | ||
" // BUG: Diagnostic contains: The method ensures the non-nullability of the fields, but returns incorrectly", |
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 don't think this should be an error. The annotation means, "If this method returns true
, then nullableItem
is guaranteed to be non-null." If the method returns false
, then the annotation says nothing about whether nullableItem
will be null or not. And at callers, we should only be using nullability information from the annotation for the case where the returned boolean matches the result
field of the annotation. So there is no need to perform any check within the method when we know it is not returning the result
value. The error reported for the return true
statement is correct. Do you agree?
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.
That does make sense to me! Let me work on it!
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.
Here: 56a0984
" @EnsuresNonNullIf(value=\"nullableItem\", result=true)", | ||
" public boolean hasNullableItem() {", | ||
" if(nullableItem != null) {", | ||
" // BUG: Diagnostic contains: The method ensures the non-nullability of the fields, but returns incorrectly", |
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.
Same comment as the one below.
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.
Here 56a0984
@mauricioaniche I think this ready to go! I made a slight simplification to the store update logic in fa7728e, based on our updated understanding of how the "then" and "else" stores work. Out of an abundance of caution, I'm going to run our benchmarking suite with this change, which involves pushing the branch up to our main repo and creating a dummy PR. Assuming there are no red flags there I'll go ahead and squash and merge. |
Hrm, it seems our benchmarking server needs a new JVM version so the benchmarking job is not working 😐 I'm not too worried about the perf impact of this change, so I will go ahead and land it. |
Great news! Thanks for your suppprt in this adventure, @msridhar ! |
This PR is an initial draft of a proposal to support
@EnsuresNonNullIf
, following the discussion on issue #1032. This annotation allows the definition of methods that ensure that fields aren't null.An example of the annotation: