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

Decide retention for our annotations #28

Closed
kevinb9n opened this issue Apr 19, 2019 · 25 comments
Closed

Decide retention for our annotations #28

kevinb9n opened this issue Apr 19, 2019 · 25 comments
Labels
design An issue that is resolved by making a decision, about whether and how something should work. dev development tasks, build issues...
Milestone

Comments

@kevinb9n
Copy link
Collaborator

No description provided.

@kevinb9n
Copy link
Collaborator Author

I'm not really aware of a reason why we would ever regret using runtime retention.

And whenever we don't, we will sooner or later be asked to.

@wmdietlGC
Copy link
Collaborator

For the Checker Framework, we provided a separate checker-qual-android artifact that uses CLASS retention, because RUNTIME seems to cause issues with the default Android configuration: typetools/checker-framework#2118 (comment)

By default, the annotations are RUNTIME retention https://checkerframework.org/manual/#faq-runtime-retention

@kevinb9n
Copy link
Collaborator Author

I think the only thing that makes sense is for us to make everything runtime retention. When users have performance-based reasons for wanting to turn that off, they can deal with that in their toolchain; I don't know if it's even any of our concern how they do.

@kevinb9n
Copy link
Collaborator Author

I believe we consider this settled for now. No-runtime-retention versions of our annotations can be hacked up as a secondary matter.

@cpovirk
Copy link
Collaborator

cpovirk commented May 8, 2020

(We now have #96 to consider a special case for nullness. It hasn't seen much activity, but I wanted to cross-link these 2 issues.)

@cpovirk
Copy link
Collaborator

cpovirk commented Dec 15, 2020

(Just to pile on: I was just reminded that Guice is an example of a library that requires runtime retention for nullness annotations. But that said: It also ignores type annotations entirely! So that's something that will affect us inside Google, at least.)

cpovirk added a commit that referenced this issue Dec 15, 2020
@cpovirk
Copy link
Collaborator

cpovirk commented Jan 25, 2021

I saw a concern internally about runtime retention on Android: Under older versions of Android, a runtime-visible annotation must appear in the same dex as all classes that use it. For widely-used nullness annotations, this would force a huge number of classes to appear in a single dex, exceeding the dex limit.

It may be that the solution to this is going to be to still use RUNTIME retention but to declare CLASS-retention aliases (using the system to be designed in #35). However, if the need for CLASS-retention aliases is going to be so large, we should at least consider providing a standard annotation, rather than requiring everyone to redeclare it.

@cpovirk
Copy link
Collaborator

cpovirk commented Jan 25, 2021

Hmm, or does the problem arise only with annotations that have an enum element?? I will try to learn more about this. If so, this is yet another reason to avoid enum elements.

@cpovirk
Copy link
Collaborator

cpovirk commented Jan 25, 2021

Ah: I think the problem with old versions of Android was indeed specific to annotations with an enum element. But the workaround for that problem in the Android build tools forces any runtime-retention annotation to be in the same dex as the classes that use it. So currently, even a plain runtime-retention @Nullable is likely to cause issues.

Perhaps the workaround could be improved, but we'd need to look into that.

@cpovirk
Copy link
Collaborator

cpovirk commented Jan 25, 2021

But wait, no? @Nullable will be a type annotation, which doesn't show up in Android's bytecode at all.

...except... our "in the club" annotation will be a declaration annotation. It will cause the same problem.

(I was starting to wonder if the "in the club" annotation could mostly be placed in package-info classes -- and, if so, whether those show up in Android bytecode. But we definitely can't assume that universally -- probably especially for protobuf (the context in which I heard about this problem), as I believe users often generate their protobufs into the same package as other code. If so, they'd really need to annotate the whole package (which can be a pain) or else opt out with a "not in the club" annotation -- which would trigger the same problem!)

@kevin1e100
Copy link
Collaborator

Note the combination of type and runtime-retention declaration annotations is somewhat scary for Android also (regardless of Android version): @DefaultNotNull declaration annotations would be visible at runtime, but any @Nullable type annotations wouldn't be, for instance. So it wouldn't be possible to determine whether a value was meant to be nullable at runtime, and in fact, the presence of @DefaultNotNull may suggest the opposite.

@cpovirk
Copy link
Collaborator

cpovirk commented Jan 26, 2021

Thanks. I recall your pointing that out in #96, and we should definitely have all these points in front of us.

I'm finding myself rapidly convinced that we need a type-use, runtime-retention Nullable (for Guice) and a declaration, class-retention InTheClub (for multidex). That handily solves the problem you've raised, too. So at the very least, I'm inclined to try that internally to see if it shakes out any other fun surprises.

We can presumably still define aliases with different retentions (or users can do it themselves), but those are feeling like the least dangerous defaults.

@cpovirk cpovirk reopened this Jan 26, 2021
@cpovirk
Copy link
Collaborator

cpovirk commented Jan 26, 2021

(Granted, this creates the reverse problem in the JRE: You can see Nullable but not InTheClub. But I'm guessing that much less code cares about InTheClub, aside from maybe a testing library or bytecode rewriter that adds runtime null checks or something. (And those could reasonably extract InTheClub from bytecode.))

cpovirk added a commit that referenced this issue Jan 27, 2021
See
#28 (comment)
and nearby comments.

This is not a final decision, either. However, since we've already
discovered a problem with `RUNTIME` in practice, I'd like to take the
opportunity to try to identify any problems with `CLASS` in practice.
That should help us make an informed final decision.
@cpovirk
Copy link
Collaborator

cpovirk commented Feb 2, 2021

CLASS retention for @InTheClub also just marginally decreases the chance of problems from split packages: Yes, users may still see build-time problems when a package-info in their tests is unexpectedly applied to their production code (or vice versa, or in other split-package scenarios). But at least we nudge users away from examining @InTheClub at runtime, where these issues are perhaps even harder to debug??

I'm not really sure I believe that, though. Maybe I just believe that looking for @InTheClub at runtime is a little scary in general?

An example other concern around runtime use of @InTheClub: I remember talking to the Guice people about whether a Guice-generated Provider<Foo> should throw an exception if the provider implementation returns null. One possibility is that it throws in @InTheClub code but not otherwise. But that's scary: It's possible to pass a Provider<Foo> from one @InTheClub code to non-@InTheClub code (and vice versa). As a result, you might think that you're getting null checking but not be (or, once again, vice versa). Plus, you might or might not get any given Provider from Guice, so you can't count on a particular behavior, anyway. If we really wanted Guice to perform such enforcement, we might be better served by a NonNullProvider<T> type.

Again, though, I'm not putting much stock in any of this.

[edit: Part of the reason I'm not too worried about this: It's not too different from the normal confusion that could arise when @InTheClub and non-@InTheClub code interact. It's perhaps somewhat worse if tools are adding actual runtime checks, though.]

@sdeleuze
Copy link
Collaborator

sdeleuze commented Feb 3, 2021

We need RUNTIME for Spring used case since we use the null-safety annotation to identify if parameters are mandatory or not, like we use the related type information for Kotlin type system.

@cpovirk
Copy link
Collaborator

cpovirk commented Feb 3, 2021

Thanks. As discussed in today's meeting, we will need to do at least one of the following before our 1.0 release:

@kevin1e100
Copy link
Collaborator

kevin1e100 commented Feb 3, 2021 via email

@cpovirk
Copy link
Collaborator

cpovirk commented Feb 3, 2021

If we put a runtime-retention annotation on every generated protobuf class (which I think we'll have to do, since those classes may share a package with other code), that's enough to hit the dex limit in at least one Google app. So I fear we need to do "something."

(For my reference: Native multidex arrived in API Level 21. Android devices with a lower API Level likely represent <5% of devices now, though I haven't found up-to-date numbers.)

Among the possibilities:

  • Currently, Android puts any class with any runtime-retention annotations in the main dex. IIUC, it could perhaps instead:
    • do that only for classes with runtime-retention annotations that have enum elements [edit: or class elements, too?? see comment 11 on internal issue 31044345, which I may have misinterpreted, and/or they may have misinterpreted the problem as being with annotation classes themselves, rather than their enum elements?] (which we expect to avoid, anyway), or
    • put a copy of the annotation (and maybe any classes and enums that it uses, hopefully without also needing their transitive dependencies?) into every dex in which it's used
  • Ask tool authors to special-case protobuf classes (and potentially other widely used classes) so that they don't need to be explicitly annotated.
  • Take one of the aliasing approaches discussed above. That could include putting a class-retention alias into protobuf itself or into its generated code. (But if it generated a separate alias for every message class it generated, might we have the same problem? Hopefully not, since tools could ignore the alias -- and its dependencies -- entirely, given its class retention?)
  • I don't know what goes into improve the main-dex-list algorithm, but that sounds great if it can be enough.

(RE: annotations on modules: Given that our type annotations are going to be entirely invisible, as you've pointed out, I suspect that we don't need to worry too much about how visible our declaration annotations are. And in fact we may prefer them not to be visible, as we've noted :) I grant that a mixture of visible and invisible (depending on whether they appear on modules or not) is worse than either option, but it feels like a small concern, relatively speaking.)

@cpovirk
Copy link
Collaborator

cpovirk commented Feb 3, 2021

I filed https://issuetracker.google.com/issues/179271759 to ask if Android could be less aggressive in putting things in the main dex. I could imagine that this is not a high priority for them, but we'll see.

@cpovirk
Copy link
Collaborator

cpovirk commented Feb 5, 2021

We converted the issue I filed into a Google-internal one so that we could share information about the specific failures I was seeing. But I have an update.

I've been informed that our internal tools (and probably external tools, too) are nowadays building the main-dex list with R8 instead of the dx code I was looking at. And the R8 code is smart enough to apply its workaround only for runtime-retention annotations that actually have enum elements.

That sounds like it should mean that we can safely use runtime retention (again, as long as we avoid enum elements, which we want to do, anyway). However, there's still the mystery of why I saw an actual "Too many classes listed in main dex list file" failure when I attempted a widely used runtime-retention annotation. The R8 code's enum check is clearly working in general, but either it's failing in this specific case, or something else is going wrong.

cpovirk added a commit that referenced this issue Feb 8, 2021
Motivation:

1. sdeleuze points out that Spring needs `RUNTIME` retention:
#28 (comment)

2. The problem I'd seen with `RUNTIME` retention is more limited than
I'd initially feared. The downside of that is that it's still not clear
when the problem actually does arise. Given that, we're probably best
served by using `RUNTIME` retention for a while and trying to identify
any commonalities from failures that we see:
#28 (comment)

This reverts commit 4fc89fd.
@netdpb netdpb added this to the 1.0 milestone Oct 13, 2021
@kevinb9n
Copy link
Collaborator Author

kevinb9n commented Nov 10, 2021

It feels like the only good solution here would have to involve runtime-retention-stripping out of our official jar as some sort of proguard-like build step. Is that feasible? Is there really an alternative? And, would we feel responsible for providing that solution?

But one way or the other, I just can't see justifying making the core artifact class-retention-only.

@kevinb9n
Copy link
Collaborator Author

kevinb9n commented Dec 8, 2021

At this point I'm exceedingly comfortable with the decision that annotations in our core artifact have runtime retention.

If the need for an altered form of the core artifact with the retention reduced (or tool that does that munging in a build) becomes compelling it can be filed separately.

@kevinb9n kevinb9n closed this as completed Dec 8, 2021
@kevinb9n kevinb9n changed the title Decide whether annotations will be runtime-retention, or runtime vs. class on case-by-case basis Decide retention for our annotations [working decision: runtime] Dec 8, 2021
@cpovirk
Copy link
Collaborator

cpovirk commented Dec 30, 2021

One thing that's convenient (which we've already said a bit about above): One of the platforms most likely to care about code size, Android, already leaves type-use annotations out of the final binary. So, if we can make it practical for most people to use type-use annotations, then there's no need for Android apps to fear runtime retention for those annotations, in contrast to what some do now:

  • javax.annotation.Nullable vs androidx.annotation.Nullable
    • Always prefer androidx.annotation.Nullable.
    • It uses @Retention(SOURCE) [cpovirk: actually, CLASS] rather than @Retention(RUNTIME).

That still leaves the possibility that @NullMarked is used often enough to cause concern, as I feared above. But if that comes up, we have options.

@kevinb9n kevinb9n modified the milestones: 1.0, 0.3 Dec 2, 2022
@kevinb9n kevinb9n added the design An issue that is resolved by making a decision, about whether and how something should work. label Mar 16, 2023
@kevinb9n kevinb9n added the dev development tasks, build issues... label Mar 13, 2024
@netdpb
Copy link
Collaborator

netdpb commented Apr 9, 2024

The current, working decision, as of 2021-12-08, is that JSpecify's annotations will have runtime retention, since some reflection-based tools, like Guice, require runtime retention for annotations.

The argument for changing this decision is that some platforms, like Android, are sensitive to code size and don't require runtime retention, so we should not impose those costs.

However, platforms or applications that do not want the code size cost of runtime-retained annotations can use tools to strip them out of binary code before deployment. We shouldn't preclude reflection-based tools from using nullness information.

This must be decided before version 1.0 of the jar.

I propose the current decision be finalized for 1.0. If you agree, please add a thumbs-up emoji (👍) to this comment. If you disagree, please add a thumbs-down emoji (👎) to this comment and briefly explain your disagreement. Please only add a thumbs-down if you feel you can make a strong case why this decision will be materially worse for users or tool providers than an alternative.

Results: Eight 👍; no other votemoji.

@netdpb
Copy link
Collaborator

netdpb commented May 21, 2024

Decision: JSpecify's annotations will have runtime retention.

@netdpb netdpb changed the title Decide retention for our annotations [working decision: runtime] Decide retention for our annotations May 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design An issue that is resolved by making a decision, about whether and how something should work. dev development tasks, build issues...
Projects
None yet
Development

No branches or pull requests

6 participants