Skip to content

Support @RequiresNonnull and @EnsuresNonnull for expressing field nullability pre- and post-conditions#423

Merged
msridhar merged 71 commits intouber:masterfrom
nimakarimipour:require_ensures_nonnull
Dec 8, 2020
Merged

Support @RequiresNonnull and @EnsuresNonnull for expressing field nullability pre- and post-conditions#423
msridhar merged 71 commits intouber:masterfrom
nimakarimipour:require_ensures_nonnull

Conversation

@nimakarimipour
Copy link
Copy Markdown
Contributor

@nimakarimipour nimakarimipour commented Sep 30, 2020

Adds support for expressing methods precondition and postcondition using @RequiresNonnull and @EnsuresNonnull annotation.

Annotations

  1. @RequiresNonnull(param) expresses a method precondition which expects the class field with name param, to be @Nonnull at call site. NullAway data flow analysis is going to assume that the specified class field is @Nonnull at 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.
  2. @EnsuresNonnull(param) expresses a method postcondition which the class field with name param will be @Nonnull at exit point of method. The @Nonnull assumption 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

class Foo {
    @Nullable Item nullItem;
    @RequiresNonnull("nullItem")
    public void run() {
            nullItem.call(); //Here NullAway does not report an error because of the assumption.
            nullItem = null;
            nullItem.call(); //Here NullAway reports an error since the assumption is no longer valid.
    }
}
class Foo {
    @Nullable Item nullItem;
    @EnsuresNonnull("nullItem") //Here NullAway injects the Nonnull value for the corresponding access path since the assumption is valid
    public void test1() {
        nullItem = new Item();
    }
    @EnsuresNonnull("nullItem") //Here NullAway reports an error since the nullItem is not Nonnull at exit point and the assumption is not valid.
    public void test2() { 
    
    }
}
class Foo {
    @Nullable Item nullItem;
    @RequiresNonnull("nullItem")
    public void run() {
        nullItem.call();
    }
    @EnsuresNonnull("nullItem")
    public void init() {
        nullItem = new Item();
    }
    public void test1() {
        init();
        run(); //Here both the precondition and postcondition are satisfied.
    }
    public void test2() {
        Foo bar = new Foo();
        bar.init();
        bar.run(); //Here both the precondition and postcondition are satisfied.
    }
    public void test3() {
        Foo bar = new Foo();
        init();
        Foo other = new Foo();
        other.init();
        bar.run();  //Here NullAway reports an error since the precondition for "run" method on "bar" instance is not satisfied.
    }
}

Updates

  1. Addressed all comments.
  2. Added the support for selecting multiple class fields in both annotations.
  3. Fixed the bug where the user might choose a field from super classes fields.
  4. Fixed the bug where a method might not be annotated as @EnsuresNonNull but its superclass does, and it was not updating the NullnessStore when the method has been invoked.
  5. 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 @EnsuresNonNull annotation. All selected class fields including super methods selected fields must be written explicitly as parameter in the sub methods @EnsuresNonNull annotation. If a method is not annotated as @EnsuresNonNull, still needs to guarantee all selected fields from super method are @NonNull at exit point if exists.
    • @RequiresNonNull: All sub methods, can only remove fields from the set of all fields mentioned as parameter in super class @RequiresNonNull annotation. 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 @RequiresNonNull annotation.
  6. Refactored the code since RequiresNonNullHandler and EnsuresNonNullHandler had a lot in common.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Sep 30, 2020

CLA assistant check
All committers have signed the CLA.

@nimakarimipour nimakarimipour changed the title Adds support for expressing methods pre-condition and post-condition using @RequiresNonnull and @EnsuresNonnull annoatation Adds support for expressing methods precondition and postcondition using @RequiresNonnull and @EnsuresNonnull annoatation Sep 30, 2020
@lazaroclapp lazaroclapp changed the title Adds support for expressing methods precondition and postcondition using @RequiresNonnull and @EnsuresNonnull annoatation Adds support for expressing methods precondition and postcondition using @RequiresNonnull and @EnsuresNonnull annotation Oct 1, 2020
Copy link
Copy Markdown
Collaborator

@msridhar msridhar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread nullaway/src/main/java/com/uber/nullaway/qual/EnsuresNonnull.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/qual/RequiresNonnull.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPath.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPath.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessAnalysis.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/handlers/EnsuresNonnullHandler.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/handlers/EnsuresNonnullHandler.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/handlers/EnsuresNonnullHandler.java Outdated
}

@Override
public void onMatchMethod(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Addressed all comments.
  2. Added the support for selecting multiple class fields in both annotations.
  3. Fixed the bug where the user might choose a field from super classes fields.
  4. Fixed the bug where a method might not be annotated as @EnsuresNonNull but its superclass does, and it was not updating the NullnessStore when the method has been invoked.
  5. 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 @EnsuresNonNull annotation. All selected class fields including super methods selected fields must be written explicitly as parameter in the sub methods @EnsuresNonNull annotation. If a method is not annotated as @EnsuresNonNull, still needs to guarantee all selected fields from super method are @NonNull at exit point if exists.
    • @RequiresNonNull: All sub methods, can only remove fields from the set of all fields mentioned as parameter in super class @RequiresNonNull annotation. 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 @RequiresNonNull annotation.
  6. Refactored the code since RequiresNonNullHandler and EnsuresNonNullHandler had a lot in common.

Comment thread nullaway/src/main/java/com/uber/nullaway/handlers/EnsuresNonnullHandler.java Outdated
Copy link
Copy Markdown
Collaborator

@msridhar msridhar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking a lot better! I haven't finished looking at all the tests yet, but here is another set of comments

Comment thread nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPath.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPath.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/handlers/ConditionHandler.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/handlers/ConditionHandler.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/handlers/ConditionHandler.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/handlers/ConditionHandler.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/handlers/ConditionHandler.java Outdated
*
* <p>
*
* <ul>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably:

* <p><ul>
*   <li> ...
*   ...
* </ul></p>

would be more readable. This looks like a ton of empty lines, unless GJF is enforcing it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your comment but actually GJF is enforcing it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, well, GJF makes sense to me 99% of the time, so I'll trust it on this one 😉

Copy link
Copy Markdown
Collaborator

@msridhar msridhar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! We just need to remember to update the wiki documentation, also with examples for the error messages one can get.

Copy link
Copy Markdown
Collaborator

@lazaroclapp lazaroclapp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 :)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for (String fieldName : fieldNames) {
Symbol.ClassSymbol classSymbol = ASTHelpers.enclosingClass(methodSymbol);
Preconditions.checkNotNull(
classSymbol, "can not find the enclosing class for method symbol: " + methodSymbol);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can not and cannot are not equivalent and this one should be cannot find ("is unable to find...", rather than "it might not find..." :) )

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface EnsuresNonNull {
String[] value();
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 😄

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to annotations. 4935c00

.doTest();
}

@Test
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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! :)

Comment thread .gitignore Outdated

# kotlin
annotations/
/annotations/
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lazaroclapp this originally led to our new com.uber.nullaway.annotations package source files getting ignored. Is this the right fix?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea. Do we have Kotlin code in this repo?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so 🙂 Should I just remove the line then?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try that :)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done :-)

@msridhar msridhar changed the title Adds support for expressing methods precondition and postcondition using @RequiresNonnull and @EnsuresNonnull annotation Support @RequiresNonnull and @EnsuresNonnull for expressing field nullability pre- and post-conditions Dec 8, 2020
@lazaroclapp lazaroclapp changed the title Support @RequiresNonnull and @EnsuresNonnull for expressing field nullability pre- and post-conditions Support expressing method pre- and postcondition using @RequiresNonnull/@EnsuresNonnull Dec 8, 2020
@lazaroclapp lazaroclapp changed the title Support expressing method pre- and postcondition using @RequiresNonnull/@EnsuresNonnull Add method pre- and postcondition using @RequiresNonnull/@EnsuresNonnull Dec 8, 2020
@lazaroclapp lazaroclapp changed the title Add method pre- and postcondition using @RequiresNonnull/@EnsuresNonnull Support @RequiresNonnull and @EnsuresNonnull for expressing field nullability pre- and post-conditions Dec 8, 2020
@msridhar msridhar merged commit d7e96e4 into uber:master Dec 8, 2020
@nimakarimipour nimakarimipour deleted the require_ensures_nonnull branch March 22, 2022 00:02
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 this pull request may close these issues.

4 participants