Support @RequiresNonnull and @EnsuresNonnull for expressing field nullability pre- and post-conditions#423
Conversation
msridhar
left a comment
There was a problem hiding this comment.
Overall looks great! I've finished a partial review through the EnsuresNonnullHandler. Some similar comments will apply to the other handler. I will try to finish the review soon but hopefully this is enough to get started
| } | ||
|
|
||
| @Override | ||
| public void onMatchMethod( |
There was a problem hiding this comment.
I think we need to check here for cases where this method overrides a superclass method, and the superclass method has @EnsuresNonNull? In such cases we need the overriding method to adhere to the @EnsuresNonNull spec from the superclass method, right?
There was a problem hiding this comment.
Hi Manu, thank you very much for your comments, I addressed them all. I believe this is ready for another review.
Please find the latest updates below from your last review:
- Addressed all comments.
- Added the support for selecting multiple class fields in both annotations.
- Fixed the bug where the user might choose a field from super classes fields.
- Fixed the bug where a method might not be annotated as
@EnsuresNonNullbut its superclass does, and it was not updating theNullnessStorewhen the method has been invoked. - Added the inheritance support for annotations:
@EnsuresNonNull: All sub methods, can only add new fields to the set of all fields mentioned as parameter in super class@EnsuresNonNullannotation. All selected class fields including super methods selected fields must be written explicitly as parameter in the sub methods@EnsuresNonNullannotation. If a method is not annotated as@EnsuresNonNull, still needs to guarantee all selected fields from super method are@NonNullat exit point if exists.@RequiresNonNull: All sub methods, can only remove fields from the set of all fields mentioned as parameter in super class@RequiresNonNullannotation. If a method is not annotated as@RequiresNonNull, it will not be processed and no check will take place when it has been invoked, even if the super method is annotated with@RequiresNonNullannotation.
- Refactored the code since
RequiresNonNullHandlerandEnsuresNonNullHandlerhad a lot in common.
msridhar
left a comment
There was a problem hiding this comment.
Looking a lot better! I haven't finished looking at all the tests yet, but here is another set of comments
| * | ||
| * <p> | ||
| * | ||
| * <ul> |
There was a problem hiding this comment.
Probably:
* <p><ul>
* <li> ...
* ...
* </ul></p>
would be more readable. This looks like a ton of empty lines, unless GJF is enforcing it.
There was a problem hiding this comment.
Thank you for your comment but actually GJF is enforcing it.
There was a problem hiding this comment.
Oh, well, GJF makes sense to me 99% of the time, so I'll trust it on this one 😉
msridhar
left a comment
There was a problem hiding this comment.
LGTM! We just need to remember to update the wiki documentation, also with examples for the error messages one can get.
lazaroclapp
left a comment
There was a problem hiding this comment.
LGTM.
There are a few notes and nits, but I am fine with this landing as is. Sorry for being the bottleneck on it these past few days 😅
| * | ||
| * <p> | ||
| * | ||
| * <ul> |
There was a problem hiding this comment.
Oh, well, GJF makes sense to me 99% of the time, so I'll trust it on this one 😉
| Preconditions.checkNotNull(classSymbol); | ||
| for (Element member : classSymbol.getEnclosedElements()) { | ||
| if (member.getKind().isField()) { | ||
| if (member.getSimpleName().toString().equals(name)) { |
There was a problem hiding this comment.
Out of curiosity: what was the resolution here? Was it really resolved? (I see it marked as resolved by @msridhar , so I assume we are fine with static fields after all?)
| if (fieldNames == null) { | ||
| return super.onDataflowVisitMethodInvocation( | ||
| node, types, context, inputs, thenUpdates, elseUpdates, bothUpdates); | ||
| } |
There was a problem hiding this comment.
It might be a bit cleaner to invert this condition:
if (fieldNames != null) {
[All the extra handling]
}
return super.onDataflowVisitMethodInvocation(
node, types, context, inputs, thenUpdates, elseUpdates, bothUpdates);
But it's probably fine either way :)
| for (String fieldName : fieldNames) { | ||
| Symbol.ClassSymbol classSymbol = ASTHelpers.enclosingClass(methodSymbol); | ||
| Preconditions.checkNotNull( | ||
| classSymbol, "can not find the enclosing class for method symbol: " + methodSymbol); |
There was a problem hiding this comment.
can not and cannot are not equivalent and this one should be cannot find ("is unable to find...", rather than "it might not find..." :) )
| @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) | ||
| public @interface EnsuresNonNull { | ||
| String[] value(); | ||
| } |
There was a problem hiding this comment.
@msridhar Separate from this PR. Do we have an issue for creating a com.uber.nullaway:annotation or com.uber.nullaway:lib maven artifact? Would be good to have these out there, together with a canonical implementation of castToNonNull, etc.
| @@ -0,0 +1,19 @@ | |||
| package com.uber.nullaway.qual; | |||
There was a problem hiding this comment.
Is this really a "Type qualifier" in the Checker Framework sense?
Shouldn't this package be "com.uber.nullaway.annotation"? (cc: @msridhar )
p.s. But I am fine landing as is if not obvious what it should be named, please let's not spend months debating whether to call it annotation or annotations 😄
| .doTest(); | ||
| } | ||
|
|
||
| @Test |
There was a problem hiding this comment.
Might be worth extracting this reminder into an issue before we both forget again: #430 (that said, cleaning up that issue tracker a bit is also a task to not forget 😅 )
| Preconditions.checkNotNull(classSymbol); | ||
| for (Element member : classSymbol.getEnclosedElements()) { | ||
| if (member.getKind().isField()) { | ||
| if (member.getSimpleName().toString().equals(name)) { |
There was a problem hiding this comment.
Absolutely ok with actual handling happening as a follow-up. Let's get this landed.
That said, thanks for double-checking and adding the sanity check! :)
|
|
||
| # kotlin | ||
| annotations/ | ||
| /annotations/ |
There was a problem hiding this comment.
@lazaroclapp this originally led to our new com.uber.nullaway.annotations package source files getting ignored. Is this the right fix?
There was a problem hiding this comment.
No idea. Do we have Kotlin code in this repo?
There was a problem hiding this comment.
I don't think so 🙂 Should I just remove the line then?
Adds support for expressing methods precondition and postcondition using @RequiresNonnull and @EnsuresNonnull annotation.
Annotations
@RequiresNonnull(param)expresses a method precondition which expects the class field with nameparam, to be@Nonnullat call site. NullAway data flow analysis is going to assume that the specified class field is@Nonnullat start point of the method. At every call site to the annotated method, NullAway checks weather the precondition is satisfied and will report an error otherwise.@EnsuresNonnull(param)expresses a method postcondition which the class field with nameparamwill be@Nonnullat exit point of method. The@Nonnullassumption of the class field at exit point will be validated by NullAway and will be reported if the assumption is not valid.NOTE: At this moment, for both annotations, only class fields for the receiver object are supported.
Syntax:
@Annotation(("this."|"")field_name)Examples
Updates
@EnsuresNonNullbut its superclass does, and it was not updating theNullnessStorewhen the method has been invoked.@EnsuresNonNull: All sub methods, can only add new fields to the set of all fields mentioned as parameter in super class@EnsuresNonNullannotation. All selected class fields including super methods selected fields must be written explicitly as parameter in the sub methods@EnsuresNonNullannotation. If a method is not annotated as@EnsuresNonNull, still needs to guarantee all selected fields from super method are@NonNullat exit point if exists.@RequiresNonNull: All sub methods, can only remove fields from the set of all fields mentioned as parameter in super class@RequiresNonNullannotation. If a method is not annotated as@RequiresNonNull, it will not be processed and no check will take place when it has been invoked, even if the super method is annotated with@RequiresNonNullannotation.RequiresNonNullHandlerandEnsuresNonNullHandlerhad a lot in common.