Hi Alejandro,
Hi Dmitry,
> Hi Alejandro,
>
>> First of all,
>> I see unnecessary (probably over-engineered) qualifiers:
>>
>> - _Null_unspecified seems to me the same as nothing.
>> If I didn't specify its nullability,
>> it's by definition unspecified. Right?
>>
>> - _Nullable seems to me also the same as nothing.
>> The language allows for a pointer to be NULL,
>> so if you don't specify if it can or not be null,
>> you better stay on the safe side and consider it as nullable.
>
> _Nullable is used in conjunction with the `#pragma clang
> assume_nonnull begin/end` pragma that flips the default:
>
> ```
> #pragma clang assume_nonnull begin
> int *global_int_ptr; // implicitly _Nonnull
> #pragma clang assume_nonnull end
> ```
>
> Within these pragma brackets, you need to use _Nullable to get the
> opposite behavior.
>
> The pragma itself is useful because it reduces the amount of noise the
> annotations introduce. When these annotations were adopted in Apple
> SDKs, it was found that in practice most pointers are non-nullable. So
> if we only had _Nonnull, we would have to annotate most pointers.
> Instead, Apple's SDKs bracket every header contents with this pragma,
> and instead annotate nullable pointers, significantly reducing the
> amount of annotations.
That's interesting. Most of my functions also tipically are full of
[[gnu::nonnull]], so the _Nonnull default seems the best thing.
However, would that be viable in old code that relies on standard C?
I think that it would, but maybe you have more experience. Do you agree
with the following?
Let's imagine a scenario where C3X specifies that non-qualified pointers
are nonnull. And there's only a qualifier, _Nullable, to allow NULL.
Asigning _Nullable to nonnull would issue a diagnostic.
I think C3X specifying that non-qualified pointers are nonnnull would
be a showstopper, I don't think it is likely to happen given how the
users and the committee value backward compatibility that C has
offered throughout the decades.
If I were to speculate what would happen if C3X did flip the default,
I think it would be treated by the community as a language fork.
Pre-C3X headers won't work correctly when included in C3X programs,
making incremental adoption of C3X syntax, as it was intended to be
used, impossible. Projects would likely invent a NULLABLE macro, which
would expand to _Nullable in C3X and nothing in earlier versions, to
enable an incremental transition.
That's why Clang introduced the pragma, enabling new rules to be
adopted incrementally.
Also, do you have any experience in avoiding to diagnose a _Nullable to
nonnull assignment _after_ explicitly comparing to NULL? I.e., allow
the following:
int *_Nullable p;
int *q;
if (!p)
q = p;
Internally at Google we have a checker based on the dataflow analysis
framework (https://lists.llvm.org/pipermail/cfe-dev/2021-October/069098.html)
that diagnoses usages of std::optional<T>::value() not guarded by
has_value(). We are planning to upstream it after we finish
upstreaming the dataflow framework itself. Ensuring guarded usage of
std::optional<T>::value() is very similar to diagnosing dereferences
of nullable pointers. I think a lot of the experience is transferable.
However, we haven't attempted to implement a pointer nullability check
yet, so I don't yet understand all corner cases that arise in real
world software.
However, fundamentally, there are a few questions that you need to answer:
- does _Nullable create a distinct type or not? It is extremely
important when you consider C++.
- do you want nullability-related diagnostics to be mandatory, or
optional? For example, a compiler is not required to issue a
diagnostic about a program that violates the constraints of
`restrict`.
If _Nullable does not create a distinct type, and `T*` is the same
type as `T* _Nullable`, then we can't rely on the regular type system
mechanisms to issue diagnostics.
If _Nullable creates a distinct type, then according to regular C type
checking rules, you would get a warning on the `q = p` assignment
regardless of the `if (!p)` check. That's how the C type system works,
it is not flow-sensitive.
If we want the diagnostics to be fllow-sensitive like in your example
(I think it would be the best choice), then we need to add a new
flow-sensitive component to the C type system. I don't think there is
a precedent for this in C right now. I'm not sure how the committee or
implementors would react to such a proposal.
Dmitri