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

Kotlinc explicit non-nullability of varargs arguments #720

Open
lazaroclapp opened this issue Jan 25, 2023 · 2 comments
Open

Kotlinc explicit non-nullability of varargs arguments #720

lazaroclapp opened this issue Jan 25, 2023 · 2 comments
Assignees

Comments

@lazaroclapp
Copy link
Collaborator

lazaroclapp commented Jan 25, 2023

Context Part 1: Var args nullability on NullAway and JSpecify

Handling of nullability of var args in NullAway is admittedly a bit hacky (see #674). There is a clear answer on what the correct way to interpret @Nullable annotations is for type-use annotations, which we currently don't follow, namely:

foo(@Nullable Object... varargs)

Means: the elements of the varargs array can be null, but the array reference itself is non-null (99.8% of the time, this is what we want, though it is possible in Java to pass a null varargs array by e.g. passing (Object[]) null)

Whereas:

foo(Object @Nullable... varargs)

Can be used to indicate that the array varargs itself is @Nullable, if absolutely needed.

For declaration annotations (i.e. not type-use), the only valid position is:

foo(@Nullable Object... varargs)

And it seems reasonable to treat it equivalently to the same syntax for type-use, both for consistency and because it's more likely than not what the developer intends.

Note: We currently interpret the annotation above to mean both the array and its elements can be nullable, i.e. we read it as what, using type-use annotations, would be written as foo(@Nullable Object @Nullable... varargs), but I think this is a bug/artifact of previous limitations and should be (carefully) fixed.

Context Part 2: Kotlinc generated jars and restrictive @NonNull annotations

The Kotlin compiler adds @Nullable and @NonNull annotations to the bytecode it produces, but only declaration annotations at the full parameter level (i.e. no annotations on generics, elements of arrays, etc). So, code like:

fun bar1(s: String?)

Becomes the equivalent of:

bar1(@Nullable String s)

And:

fun bar1(s: String)

Becomes the equivalent of:

bar1(@NotNull String s)

(The annotations in question are org.jetbrains.annotations.Nullable and org.jetbrains.annotations.NotNull)

This is generally a big help to NullAway, when configured with -XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=..., as it can use kotlinc added nullness information to reason about methods returning nullable or taking non-null arguments.

The issue:

Unfortunately, the following Kotlin code:

fun baz(vararg args: Any?)

Actually becomes:

  public static void baz(java.lang.Object...);
      [...]
    RuntimeInvisibleParameterAnnotations:
      parameter 0:
        0: #30()
          org.jetbrains.annotations.NotNull

Which is the equivalent of:

baz(@NotNull Object... args)

because kotlinc interprets the annotation as applying to the first parameter args as a whole and the var args array itself is non-null.

The solution(s):

  1. We could maybe get kotlinc to change the default meaning of declaration annotations in var args to match NullAway's (desired) semantics. However, even if they agree with our interpretation, this would be a big breaking change to their jar ABI.
  2. We could generally have NullAway interpret foo(@Nullable Object... varargs) with a declaration annotation as foo(Object @Nullable... varargs) with a type-use annotation. This is very ugly and confusion prone from the point of view of developers using either type of annotation, or worse, a mixture of both in different projects/targets.
  3. More realistically: We could hack into our restrictive annotations handler some knowledge of this issue, making it ignore either any non-null annotation or specifically org.jetbrains.annotations.NotNull when looking at a varargs argument.

Neither option is perfect, but this is a rather long brain-dump to convince myself that the hack from Option 3 is the best we can do here (and to document why).

@lazaroclapp lazaroclapp self-assigned this Jan 25, 2023
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.
@lazaroclapp
Copy link
Collaborator Author

We have applied fix number 3 here. Wonder if we should just close this issue and move on or leave it open and report the potentially confusing annotation to Kotlin maintainers.

@msridhar
Copy link
Collaborator

msridhar commented Feb 3, 2023

IMO we can close this as long as #674 tracks the remaining known issues with our handling of varargs. We should track reporting the Kotlin issue separatley.

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

No branches or pull requests

2 participants