-
Notifications
You must be signed in to change notification settings - Fork 26
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
Ways to mark a method which promises to throw on a null argument #226
Comments
As a note, the way NullAway handles this is to support a subset of JetBrains's
I imagine there will be some hesitancy to make JSpecify dependent on something like JetBrains's For example, we regularly see usage of the following specs:
Wonder if we want something like |
Good point about I'm going to predict that @kevinb9n will propose keeping this thread focused on the question of whether "promise to throw NPE" is a requirement, with the discussion of the "API" in a separate issue. Maybe we should start that thread now, along with other threads for the other possible requirements. Kevin? (For searchability: In CF terminology, we're talking about (part of) |
If this is two separate questions: a) Should there be a JSpecify mechanism to mark a method as throwing an exception when passed and b) What should that mechanism look like? Then my take on (a) is unambiguously that there should be. Tools already have different ways to handle this case, showing that it matters in practice, and it does happen at the interface between clients and libraries, which suggests that we might want a uniform way to handle it across tools. Certainly not a 1.0/MVP requirement, but seems well within scope of JSpecify. My take on (b) is less clear. NullAway uses On the other hand, NullAway does implement a version of |
I think it's probably fair to treat the Of course every checker should be absolutely free to respect any other |
|
(I believe this annotation does not want to dictate what exception type has to be thrown. All that matters is that the method will not complete normally in that case.) |
My 2¢:
I guess, for me the main question is: do we feel that the presence of this marker annotation is essential for completeness of JSpecify's basic nullness semantics? I can say that in out internal code the number of methods that actually require a contract like |
Well, yes and no. Annotations you put in your file only to benefit that file itself have less need to be standardized. It is annoying when you and your teammate prefer different tools, but you could at least just agree on one. Annotations you put in your file for the benefit of dependent files (which basically is why we're talking about the APIs) are different and you do need a standardized answer. If
Indeed. And with a whole contract language we'd have a pile of those decisions to make all up front just to get it off the ground. Annotations seem to make it easier to just go one by one. Maybe "drawing the line" is like this?: if at least two different tool owners feel motivated enough to add/support the annotation themselves, then better standardize it?
Yeah I do think this question is why it's been labeled post-1.0.
(As noted above, maybe the value scales with the # of calls to those methods though. Except that the most popular such methods would be the ones checkers are hardcoding anyway.) |
Yes. I guess the TL;DR: of my comment is that: since people usually don't roll out their own On the other hand, if we do end up standardising whatever
Supporting it will take us a couple LOCs, so no big deal. But also I don't think it'll allow us to remove any of the hard-coded configs we already have; at least in the foreseeable future. |
Makes sense and imho this is the right way to think. I think leaving this open but tagged Post-1.0 seems appropriate. We could further have distinct labels for "core nullness" vs "non-core nullness" or something, but for now, seems good. (I sometimes comment on non-1.0 issues but I do spend most of my "what to do next" time looking at a 1.0 view.) When it comes to eliminating hard coding, just for the remaining hardcoded lists to "feel like they have an expiration date" would be a good state to reach, even if a very distant one. |
(nothing exciting here; just cross-referencing discussions and recording a false start in my thinking)
#309 (comment) is a reminder that there's at least one family of common methods that throw something other than I want to say that the Checker Framework does dataflow based on specific exception types. But after all of 30 seconds of thought, I'm not sure that it could actually benefit from knowing the specific type of exception that is thrown in case of a null argument: Suppose that an argument [edit: But see also #309 (comment), in which maybe I can see something that the exception type could give some tools, arguably, someday, maybe?] |
I think an annotation that says that a method throws 100% of the time if a given parameter is null does have value as a form of documentation, so I support it. I'm much more ambivalent on the exception type parameter than I previously was. In theory, a tool could suppress a "nullable value passed as non-nullable method parameter" warning if the method call is inside a The benefit of specifying the exception type is theoretical and if implemented would be marginal at best. I think it probably doesn't matter. I'm leaning in the direction that a standardized way for tools to share detailed semantic information about library methods (like "Throws SomeException when parameter X is null") completely independent of annotations would be more valuable for these edge cases than creating a new annotation for every edge case. That is also outside the scope of JSpecify. JSpecify annotations should be broadly applicable and have simple, easy to understand semantics. |
Given the amount of time it takes to annotate a library for nullness, and given that we'd ideally want the annotation discussed here to be enforced by tools, and given the amount of noise it would introduce to put One of the challenges we're facing now is how to update Kotlin code that would fail to compile after we add nullness information. For example, once Kotlin knows that a method parameter has a non-nullable type, it won't let us pass nullable values anymore. The obvious solution is to add Anyway, my point here is that it would be convenient if our tooling knew that [*] We could make the annotation burden less bad by having a |
@cpovirk I can't speak to Kotlin, but don't think the majority of Java projects would need to write this annotation at all; they could rely on standard nullability annotations. I don't see a need for a
I think annotating the Java standard library and the most-used utility libraries like Apache Commons, Guava, etc. would provide 99% of the potential value of this annotation. I imagine static analysis tools already hard-code this information for the most common APIs, so that would reduce their workload. Applications and non-utility libraries wouldn't have to annotate their APIs with it, except possibly their own utility methods, but would still benefit from consuming libraries that do. |
@Mooninaut is right that a good portion of the For example in Eclipse: @Nullable Object o = ...;
Objects.requireNonNull(o); // no assignment back to o
out.println(o.toString()); // Eclipse and most know that o is now nonnull. Ditto for But if you write your own For other use cases I'm not sure I understand other than general bug prevention which seems out of scope for JSpecify. |
I agree that most of the value is tied up in recognizing very common methods like After that, the next most important thing is likely the ability to specify that The alternative use case that I was discussing in #226 (comment) would have some value to the migration we're currently doing. But it's likely that we can get a lot of the value there by hard-coding specific APIs, too. Now, if we envision a future world in which lots of people need to automatically update lots of Kotlin code to "suppress" its nullness errors, then there might be more cause for an annotation for that use case. But it's possible that we can still get most of the value from hard-coding, and we may be able to get some of the remaining value from analyzing the source code or bytecode of the methods to identify those that obviously throw NPE for a null input. |
I can't decide if this is tangental or relevant as I'm less familiar with this area but many of the See checker unlike most others has I and I think many others use I guess what I'm getting at or realizing is that there or more use cases and interpretations of these kinds of methods:
The last one I consider pretty fringe. I guess my question is what group is under discussion here for say google's (insert other ... I don't know kotlin that well)? My repeating concern of JSpecify and in general is consensus on annotating libraries because arguably even JSpecify could make |
I don't know if this will serve as much of an answer, but maybe it gives you an idea of where at least one person's head is. I've found it clarifying to have multiple nullness analyzers in mind. (I think I've heard it said that you don't have a standard unless you have multiple implementations :)) Google uses a few in different places, but the ones I'm most familiar with are the Checker Framework and Kotlin. One thing that's stuck out to me about Kotlin is the idea that nullness-augmented types can be made to be "real runtime types": If you have a Kotlin function that declares a non-null parameter, then the Kotlin compiler will automatically insert a runtime check at the beginning of that method's implementation, throwing (Given that, and given a few historical discussions (which include discussion of your "casting" use case), I'm comfortable stating that Compare also similar cases in which null is not always safe, like And yes, group 3 is pretty weird—things like "This One way to support both tools better is to support #113 in the future. Then we could say that a method like |
(more on some of those topics in https://github.com/jspecify/jspecify/wiki/nullness-borderline-cases and in the nullness design FAQ under "How are we supposed to eradicate NullPointerExceptions if even a method with all nullable parameters is still allowed to throw it?.") |
Dumping one other note for the future: Caveats:
One unfortunate fact about the Java Collections API is that sometimes we have methods like (IIRC, there is another complication here, which is that some implementations don't override all the relevant methods themselves, so there is sometimes no method for us to annotate. Oh, and plus, users often declare a field or local variable to hold a general type, like This works because, even though checkers are likely to produce errors for an override (Hmm, I'm suddenly remembering that there was also a philosophical discussion about all this :)) But we can imagine cases in which a language might be stricter about such things. If so, we'd really like for the superclass and subclass to agree. And "clearly" they need to agree on But then users of the null-rejecting subclasses get no protection against NPEs. So what if we were to annotate them as But would that even help most users? If we expect users to want to pass nullable values to other methods that are annotated in the same way (like So I think this goes nowhere. But I wanted to write it down while it was fresh in my mind. |
(Also, note that this annotation would be insufficient to accomplish another goal that tools might have: Tools like the Checker Framework like to annotate methods like |
@cpovirk I wonder when we hit diminishing returns on finer and finer-grained single-purpose annotations and increasing returns on Regarding the difference between Annotating an API method that it throws if parameter X is null allows static tools to warn against passing nullable values. The information flows from the tool to the programmer. Annotating a null-checking method that it throws if its parameter is null effectively casts the parameter value from nullable to non-null, suppressing further nullness warnings on that value. The information flows from the programmer to the tool. While the runtime effect of calling either method with null is the same (NPE), the use cases are different. I don't want a nullness warning/error on a null checking method (unless the value is statically always null), but I do want one for the API method. To give absurdly verbose names, we want |
Current decision: This feature is not required for JSpecify 1.0. Proposal for 1.0: Finalize the current decision. 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. |
Our overarching design direction is to capture nullness data through types alone, and have those types work as analogously to base types as possible.
This gives us most of what we need, but a few things are missing.
One is: if a method actually promises it will not complete normally upon null input (or maybe more specifically promises to throw NPE?), that is valuable knowledge to capture somehow, and our current annotations don't provide for it.
Oddly, all four combinations are valid between this and
@Nullable
itself. Some methods likeDouble.valueOf
could make this promise for a non-nullable parameter. But our friendscheckNotNull
and the like want to make this promise for a nullable parameter!A checker can use this information. Particularly, for the
checkNotNull
case, it would then be able to "learn" that the type of a variable becomes non-nullable below the place where it was passed to that method. This is the main motivating case I think.I believe that the various worries some of us had about
checkNotNull
being nullable can be addressed this way instead (and it will be nullable).The text was updated successfully, but these errors were encountered: