forked from typetools/checker-framework
-
Notifications
You must be signed in to change notification settings - Fork 29
Add @NonNullIfReturn: parameter-level conditional postcondition for nullness #1530
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
Open
HenryXi1
wants to merge
7
commits into
eisop:master
Choose a base branch
from
HenryXi1:add-parameter-annotation-for-conditional-nullness
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
c20f8ae
Add @NonNullIfReturn parameter annotation for conditional nullness
HenryXi1 387df91
Update contributors.tex
HenryXi1 02bcc57
Merge branch 'master' into add-parameter-annotation-for-conditional-n…
wmdietl d09e695
Add caching to parameter level conditional postconditions
HenryXi1 25a379b
Change parameter contract name to @NotNullIfReturns
HenryXi1 781b000
Add max size and LRU policy to caching
HenryXi1 1eecfca
Add support for NonNullIfReturnsTrue and NonNullIfReturnsFalse
HenryXi1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
82 changes: 82 additions & 0 deletions
82
checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNullIfReturns.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| package org.checkerframework.checker.nullness.qual; | ||
|
|
||
| import org.checkerframework.framework.qual.ParameterConditionalPostconditionAnnotation; | ||
|
|
||
| import java.lang.annotation.Documented; | ||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| /** | ||
| * A <b>parameter</b> contract: expresses that the parameter is non-null when the method returns the | ||
| * given value. | ||
| * | ||
| * <p><b>Semantic meaning:</b> {@code @NonNullIfReturns(v)} means: <i>this parameter is non-null | ||
| * when the method returns v</i>. That is: {@code method returns value() ⇒ parameter != null}. | ||
| * | ||
| * <p>This annotation only makes sense on parameters whose root type is {@code @Nullable} or | ||
| * parametric. The checker refines the parameter's type to {@code @NonNull} in code paths where the | ||
| * method's return value is known (e.g., inside {@code if (method(arg))} or {@code if | ||
| * (!method(arg))} blocks). | ||
| * | ||
| * <p><b>Example 1: hasLength</b> — param is non-null when method returns true. | ||
| * | ||
| * <pre>{@code | ||
| * public static boolean hasLength(@NonNullIfReturns(true) @Nullable String str) { | ||
| * return (str != null && !str.isEmpty()); | ||
| * } | ||
| * }</pre> | ||
| * | ||
| * When {@code hasLength(s)} returns true, {@code s} was non-null. | ||
| * | ||
| * <p><b>Example 2: equals</b> — param is non-null when method returns true. | ||
| * | ||
| * <pre>{@code | ||
| * @Override | ||
| * public boolean equals(@NonNullIfReturns(true) @Nullable Object other) { | ||
| * return this == other; | ||
| * } | ||
| * }</pre> | ||
| * | ||
| * <p><b>Example 3: isNull</b> — param is non-null when method returns false. | ||
| * | ||
| * <pre>{@code | ||
| * public static boolean isNull(@NonNullIfReturns(false) @Nullable Object obj) { | ||
| * return obj == null; | ||
| * } | ||
| * }</pre> | ||
| * | ||
| * When {@code isNull(obj)} returns false, {@code obj} was non-null. | ||
| * | ||
| * <ul> | ||
| * <li><b>Scope:</b> The annotation applies only to the parameter on which it is written. | ||
| * <li><b>Meaning:</b> {@code @NonNullIfReturns(v)} means: param is non-null when method returns | ||
| * v. | ||
| * <li><b>Target:</b> Only formal parameters. | ||
| * </ul> | ||
| * | ||
| * <p>For a fixed return value, use {@link NonNullIfReturnsTrue} or {@link NonNullIfReturnsFalse} | ||
| * instead. | ||
| * | ||
| * @see NonNullIfReturnsTrue | ||
| * @see NonNullIfReturnsFalse | ||
| * @see EnsuresNonNullIf | ||
| * @see NonNull | ||
| * @see Nullable | ||
| * @see org.checkerframework.checker.nullness.NullnessChecker | ||
| * @checker_framework.manual #nullness-checker Nullness Checker | ||
| */ | ||
| @Documented | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| @Target({ElementType.PARAMETER}) | ||
| @ParameterConditionalPostconditionAnnotation(qualifier = NonNull.class) | ||
| public @interface NonNullIfReturns { | ||
| /** | ||
| * The return value under which the parameter is non-null. | ||
| * | ||
| * @return the return value under which the parameter has the qualifier (e.g., {@code true} for | ||
| * {@code hasLength}, {@code false} for {@code isNull}) | ||
| */ | ||
| boolean value(); | ||
| } |
24 changes: 24 additions & 0 deletions
24
...-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNullIfReturnsFalse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package org.checkerframework.checker.nullness.qual; | ||
|
|
||
| import org.checkerframework.framework.qual.ParameterConditionalPostconditionAnnotation; | ||
|
|
||
| import java.lang.annotation.Documented; | ||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| /** | ||
| * A <b>parameter</b> contract: the parameter is non-null when the method returns {@code false}. | ||
| * | ||
| * <p>Equivalent to {@code @NonNullIfReturns(false)} but with a fixed value for use when the return | ||
| * condition is always false (e.g. {@code isNull}). | ||
| * | ||
| * @see NonNullIfReturns | ||
| * @see NonNullIfReturnsTrue | ||
| */ | ||
| @Documented | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| @Target({ElementType.PARAMETER}) | ||
| @ParameterConditionalPostconditionAnnotation(qualifier = NonNull.class, result = false) | ||
| public @interface NonNullIfReturnsFalse {} |
24 changes: 24 additions & 0 deletions
24
...r-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNullIfReturnsTrue.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package org.checkerframework.checker.nullness.qual; | ||
|
|
||
| import org.checkerframework.framework.qual.ParameterConditionalPostconditionAnnotation; | ||
|
|
||
| import java.lang.annotation.Documented; | ||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| /** | ||
| * A <b>parameter</b> contract: the parameter is non-null when the method returns {@code true}. | ||
| * | ||
| * <p>Equivalent to {@code @NonNullIfReturns(true)} but with a fixed value for use when the return | ||
| * condition is always true (e.g. {@code hasLength}, {@code equals}). | ||
| * | ||
| * @see NonNullIfReturns | ||
| * @see NonNullIfReturnsFalse | ||
| */ | ||
| @Documented | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| @Target({ElementType.PARAMETER}) | ||
| @ParameterConditionalPostconditionAnnotation(qualifier = NonNull.class, result = true) | ||
| public @interface NonNullIfReturnsTrue {} |
60 changes: 60 additions & 0 deletions
60
...java/org/checkerframework/framework/qual/ParameterConditionalPostconditionAnnotation.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| package org.checkerframework.framework.qual; | ||
|
|
||
| import java.lang.annotation.Annotation; | ||
| import java.lang.annotation.Documented; | ||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| /** | ||
| * A meta-annotation that indicates that an annotation E is a <b>parameter</b> conditional | ||
| * postcondition annotation: E is written on a formal parameter and expresses that the annotated | ||
| * parameter has a given qualifier when the method returns a given boolean value. | ||
| * | ||
| * <p>E either has a single element {@code value()} of type {@code boolean} with the same meaning as | ||
| * {@link EnsuresQualifierIf#result()}, or E has no such element and the result is fixed by this | ||
| * meta-annotation's {@link #result()} (for concrete annotations like | ||
| * {@code @NonNullIfReturnsTrue}). The expression to which the qualifier applies is implicit: it is | ||
| * the annotated parameter (e.g. {@code "#1"} for the first parameter). | ||
| * | ||
| * <p>For example, the nullness checker defines {@link | ||
| * org.checkerframework.checker.nullness.qual.NonNullIfReturns}: | ||
| * | ||
| * <pre><code> | ||
| * {@literal @}ParameterConditionalPostconditionAnnotation(qualifier = NonNull.class) | ||
| * {@literal @}Target(ElementType.PARAMETER) | ||
| * public {@literal @}interface NonNullIfReturns { | ||
| * boolean value(); | ||
| * } | ||
| * </code></pre> | ||
| * | ||
| * <p>Usage: {@code @NonNullIfReturns(true)} or {@code @NonNullIfReturnsTrue} means the parameter is | ||
| * non-null when the method returns true; {@code @NonNullIfReturns(false)} or | ||
| * {@code @NonNullIfReturnsFalse} when it returns false. | ||
| * | ||
| * @see ConditionalPostconditionAnnotation | ||
| * @see EnsuresQualifierIf | ||
| */ | ||
| @Documented | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| @Target({ElementType.ANNOTATION_TYPE}) | ||
| public @interface ParameterConditionalPostconditionAnnotation { | ||
| /** | ||
| * The qualifier that is established on the annotated parameter when the method returns the | ||
| * value specified by the parameter annotation's {@code value()} element, or by this {@link | ||
| * #result()} when the annotation has no {@code value()} (e.g. {@code @NonNullIfReturnsTrue}). | ||
| * | ||
| * @return the qualifier | ||
| */ | ||
| Class<? extends Annotation> qualifier(); | ||
|
|
||
| /** | ||
| * The return value under which the qualifier holds. Used when the concrete annotation has no | ||
| * {@code value()} element (e.g. {@code @NonNullIfReturnsTrue} / | ||
| * {@code @NonNullIfReturnsFalse}). Ignored when the concrete annotation has {@code value()}. | ||
| * | ||
| * @return true if the qualifier holds when the method returns true, false when it returns false | ||
| */ | ||
| boolean result() default true; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| // Tests @NonNullIfReturns, @NonNullIfReturnsTrue, and @NonNullIfReturnsFalse (parameter-level | ||
| // conditional postconditions) for both correct refinement and expected errors. | ||
|
|
||
| import org.checkerframework.checker.nullness.qual.NonNull; | ||
| import org.checkerframework.checker.nullness.qual.NonNullIfReturns; | ||
| import org.checkerframework.checker.nullness.qual.NonNullIfReturnsFalse; | ||
| import org.checkerframework.checker.nullness.qual.NonNullIfReturnsTrue; | ||
| import org.checkerframework.checker.nullness.qual.Nullable; | ||
|
|
||
| public class NonNullIfReturnsTest { | ||
|
|
||
| // ----------------------------------------------------------------------- | ||
| // @NonNullIfReturns(true) / (false) | ||
| // ----------------------------------------------------------------------- | ||
|
|
||
| public static boolean hasLength(@NonNullIfReturns(true) @Nullable String str) { | ||
| return (str != null && !str.isEmpty()); | ||
| } | ||
|
|
||
| void useHasLength(@Nullable String s) { | ||
| if (hasLength(s)) { | ||
| @NonNull String n = s; | ||
| n.hashCode(); | ||
| } else { | ||
| // :: error: (assignment.type.incompatible) | ||
| @NonNull String n = s; | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(@NonNullIfReturns(true) @Nullable Object other) { | ||
| return this == other; | ||
| } | ||
|
|
||
| void useEquals(NonNullIfReturnsTest a, @Nullable Object b) { | ||
| if (a.equals(b)) { | ||
| @NonNull Object n = b; | ||
| n.hashCode(); | ||
| } | ||
| } | ||
|
|
||
| public static boolean isNull(@NonNullIfReturns(false) @Nullable Object obj) { | ||
| return obj == null; | ||
| } | ||
|
|
||
| void useIsNull(@Nullable Object p) { | ||
| if (!isNull(p)) { | ||
| @NonNull Object n = p; | ||
| n.hashCode(); | ||
| } else { | ||
| // :: error: (assignment.type.incompatible) | ||
| @NonNull Object n = p; | ||
| } | ||
| } | ||
|
|
||
| public static boolean bothNonNull( | ||
| @NonNullIfReturns(true) @Nullable Object a, | ||
| @NonNullIfReturns(true) @Nullable Object b) { | ||
| return a != null && b != null; | ||
| } | ||
|
|
||
| void useBothNonNull(@Nullable Object x, @Nullable Object y) { | ||
| if (bothNonNull(x, y)) { | ||
| @NonNull Object nx = x; | ||
| @NonNull Object ny = y; | ||
| nx.hashCode(); | ||
| ny.hashCode(); | ||
| } else { | ||
| // :: error: (assignment.type.incompatible) | ||
| @NonNull Object nx = x; | ||
| // :: error: (assignment.type.incompatible) | ||
| @NonNull Object ny = y; | ||
| } | ||
| } | ||
|
|
||
| // Checker trusts the annotation; does not verify method body. | ||
| public boolean alwaysTrue(@NonNullIfReturns(true) @Nullable Object obj) { | ||
| return true; | ||
| } | ||
|
|
||
| void useAlwaysTrue(@Nullable Object p) { | ||
| if (alwaysTrue(p)) { | ||
| @NonNull Object n = p; | ||
| n.hashCode(); | ||
| } else { | ||
| // :: error: (assignment.type.incompatible) | ||
| @NonNull Object n = p; | ||
| } | ||
| } | ||
|
|
||
| // ----------------------------------------------------------------------- | ||
| // @NonNullIfReturnsTrue | ||
| // ----------------------------------------------------------------------- | ||
|
|
||
| public static boolean hasLengthTrue(@NonNullIfReturnsTrue @Nullable String str) { | ||
| return (str != null && !str.isEmpty()); | ||
| } | ||
|
|
||
| void useHasLengthTrue(@Nullable String s) { | ||
| if (hasLengthTrue(s)) { | ||
| @NonNull String n = s; | ||
| n.hashCode(); | ||
| } else { | ||
| // :: error: (assignment.type.incompatible) | ||
| @NonNull String n = s; | ||
| } | ||
| } | ||
|
|
||
| public static boolean bothNonNullTrue( | ||
| @NonNullIfReturnsTrue @Nullable Object a, @NonNullIfReturnsTrue @Nullable Object b) { | ||
| return a != null && b != null; | ||
| } | ||
|
|
||
| void useBothNonNullTrue(@Nullable Object x, @Nullable Object y) { | ||
| if (bothNonNullTrue(x, y)) { | ||
| @NonNull Object nx = x; | ||
| @NonNull Object ny = y; | ||
| nx.hashCode(); | ||
| } else { | ||
| // :: error: (assignment.type.incompatible) | ||
| @NonNull Object nx = x; | ||
| // :: error: (assignment.type.incompatible) | ||
| @NonNull Object ny = y; | ||
| } | ||
| } | ||
|
|
||
| // ----------------------------------------------------------------------- | ||
| // @NonNullIfReturnsFalse | ||
| // ----------------------------------------------------------------------- | ||
|
|
||
| public static boolean isNullFalse(@NonNullIfReturnsFalse @Nullable Object obj) { | ||
| return obj == null; | ||
| } | ||
|
|
||
| void useIsNullFalse(@Nullable Object p) { | ||
| if (!isNullFalse(p)) { | ||
| @NonNull Object n = p; | ||
| n.hashCode(); | ||
| } else { | ||
| // :: error: (assignment.type.incompatible) | ||
| @NonNull Object n = p; | ||
| } | ||
| } | ||
|
|
||
| public static boolean neitherNull( | ||
| @NonNullIfReturnsFalse @Nullable Object a, @NonNullIfReturnsFalse @Nullable Object b) { | ||
| return a == null || b == null; | ||
| } | ||
|
|
||
| void useNeitherNull(@Nullable Object x, @Nullable Object y) { | ||
| if (!neitherNull(x, y)) { | ||
| @NonNull Object nx = x; | ||
| @NonNull Object ny = y; | ||
| nx.hashCode(); | ||
| } else { | ||
| // :: error: (assignment.type.incompatible) | ||
| @NonNull Object nx = x; | ||
| // :: error: (assignment.type.incompatible) | ||
| @NonNull Object ny = y; | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,6 +51,7 @@ | |
| Hamed Taghani, | ||
| Hannes Greule, | ||
| Heath Borders, | ||
| Henry Xi, | ||
| Ivory Wang, | ||
| Jakub Vr\'ana, | ||
| James Yoo, | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
We might want
NonNullIfReturnsTrueandNonNullIfReturnsFalseas concrete annotations.How could we re-model this to support a fixed truth value?