Skip to content
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

Not-null varargs argument with Nullable elements - considered as Nullable #674

Closed
krisso-rtb opened this issue Oct 11, 2022 · 8 comments · Fixed by #1025
Closed

Not-null varargs argument with Nullable elements - considered as Nullable #674

krisso-rtb opened this issue Oct 11, 2022 · 8 comments · Fixed by #1025
Labels
bug jspecify Related to support for jspecify standard (see jspecify.dev)

Comments

@krisso-rtb
Copy link

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE_USE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
        ElementType.LOCAL_VARIABLE })
public @interface Nullable {
}
--------------------------------------------------------------------------
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE_USE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
        ElementType.LOCAL_VARIABLE })
public @interface NonNull {
}
--------------------------------------------------------------------------
protected static String composeName(@Nullable String @NonNull... names) {
    StringJoiner stringJoiner = new StringJoiner(":");
    for (String name : names) {
        if (name != null && !name.isEmpty()) {
            stringJoiner.add(name);
        }
    }
    return stringJoiner.toString();
}

result:

error: [NullAway] enhanced-for expression names is @Nullable

which points to line:

for (String name : names) {

Key facts:

  • names is vararg array, it's expected to be not-null and contain:
  • Nullable String elements
  • For some reason these two annotations are mixed.

Nice examples:
https://help.eclipse.org/latest/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftask-using_null_type_annotations.htm&cp=1_3_9_1_3&anchor=compatibility

There is also one answer I found helpful:
https://stackoverflow.com/questions/32327134/where-does-a-nullable-annotation-refer-to-in-case-of-a-varargs-parameter

@msridhar
Copy link
Collaborator

Yes, NullAway doesn't properly parse annotations on arrays (or varargs); it interprets the @Nullable annotation in your example as pertaining to the reference to the array itself, not to the references within the array. Unfortunately fixing this will introduce compatibility issues, so we need to be careful. We plan to do this as part of our efforts to support JSpecify. Thanks for the report!

@msridhar msridhar added bug jspecify Related to support for jspecify standard (see jspecify.dev) labels Oct 27, 2022
@cpovirk
Copy link

cpovirk commented Dec 22, 2022

This is probably not a surprise, but I notice the same thing with annotations on type arguments: They're interpreted as if they're on the "top-level" type. For example:

package p;

import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;

class TypeArgumentAnnotation {
  void use(List<@Nullable String> list) {
    list.hashCode();
  }
}
TypeArgumentAnnotation.java:8: error: [NullAway] dereferenced expression list is @Nullable
    list.hashCode();
        ^

@msridhar
Copy link
Collaborator

Wow @cpovirk that was not expected. I am going to pull that out as a separate issue.

@msridhar
Copy link
Collaborator

@cpovirk the issue you observed was fixed in #702

lazaroclapp added a commit that referenced this issue Jan 26, 2023
See #720 for a detailed explanation.

Long story short: 

1. `kotlinc` will add `@org.jetbrains.annotations.NotNull` as a declaration annotation for any argument that is non-null, which we pick up from jars when running with `AcknowledgeRestrictiveAnnotations=true`. 
2. Unfortunately, it will mark code such as `open fun w(vararg args: Any?)` as having `@NotNull args` (meaning the array itself). 
3. This is indistinguishable at the bytecode level from Java code such as  `w(@NotNull Object... args)`, which we believe should be interpreted as marking the elements of `args` as being `@NotNull`.
4. This PR hacks `RestrictiveAnnotationHandler` to skip `@org.jetbrains.annotations.NotNull` on var args only.

Additionally, our basic handling of var args is pretty broken. I went over our test cases and commented where I think our current behavior is different from the desired behavior as documented in #720 and #674. Foregoing fixing it for now, as I am worried about the breaking change and I think we want to avoid changing that before 0.10.9.
@cpovirk
Copy link

cpovirk commented Mar 21, 2024

The main fix here does still need to be in NullAway, but I did notice one other thing when I was looking back at this issue:

Because the @Nullable annotation in this example includes PARAMETER (as well as TYPE_USE) in its @Target, it does get applied to the entire parameter, too. We can see in the .class file that the parameter has @Nullable on it. That's in addition to the @NonNull annotation on the parameter type and to the @Nullable annotation on the parameter's element type. That means that the array is annotated as both nullable and non-null. I don't know how NullAway normally handles such conflicts. But you could avoid that entirely by removing PARAMETER from the @Target.

(If you still have tools that require PARAMETER in the @Target, you might be able to use a separate @Nullable annotation for them. Unfortunately, whenever you have annotations that include both TYPE_USE and other target locations, you may encounter cases in which they're applied to multiple locations.)

@msridhar
Copy link
Collaborator

Thanks a lot @cpovirk! We will keep an eye out for that issue. I would have been very confused without your comment. I hope to get back to this issue at some point soon.

@msridhar
Copy link
Collaborator

msridhar commented Aug 18, 2024

Similar to #1010 (comment) here is a table of expected behaviors with varargs annotations:

Annotation Type Annotation Position Mode @Nullable varargs array @Nullable array elements @Nullable args at calls
declaration - standard
declaration - legacy
declaration - JSpecify
type use before ... standard
type use before ... legacy
type use before ... JSpecify
type use elements standard
type use elements legacy
type use elements JSpecify
type use both standard
type use both legacy
type use both JSpecify
both before ... standard
both before ... legacy
both before ... JSpecify
both elements standard
both elements legacy
both elements JSpecify
both both standard
both both legacy
both both JSpecify

A subtlety here is that a declaration annotation should allow @Nullable arguments to be passed at call sites outside of JSpecify mode, even though we cannot check within the method that they are used correctly. This is because in almost all cases, one wants to write @Nullable on the varargs formal parameter to indicate that individual varargs actual parameters may be @Nullable.

@msridhar
Copy link
Collaborator

This is finally fixed in NullAway 0.12.0!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug jspecify Related to support for jspecify standard (see jspecify.dev)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants