ISO C3X proposal: nonnull qualifier

Hi all,

I'd like to propose the following feature for ISO C (and also ISO C++).
It is based on a mix of GCC's [[gnu::nonnull]] and Clang's _Nonnull,
with a pinch of salt of mine.

I'd like to get some feedback from GCC and Clang,
before sending it as an official proposal.

BTW, since the working group is probably very busy with C2X,
I may delay sending it more than a year.
Or I may propose it first to ISO C++,
and then to ISO C.

I wrote the initial draft in the form of a manual page,
whose source code can be found here:
<https://github.com/alejandro-colomar/nonnull&gt;

It has a Makefile to easily transform it into a PDF.
I also rendered it with cat to inline it in this email.

Cheers,
Alex

Hi,

Hi all,

I'd like to propose the following feature for ISO C (and also ISO C++).
It is based on a mix of GCC's [[gnu::nonnull]] and Clang's _Nonnull,
with a pinch of salt of mine.

I'd like to get some feedback from GCC and Clang,
before sending it as an official proposal.

BTW, since the working group is probably very busy with C2X,
I may delay sending it more than a year.
Or I may propose it first to ISO C++,
and then to ISO C.

I wrote the initial draft in the form of a manual page,
whose source code can be found here:
<https://github.com/alejandro-colomar/nonnull&gt;

It has a Makefile to easily transform it into a PDF.
I also rendered it with cat to inline it in this email.

I just came up with some addition to the initial draft:

$ git diff
diff --git a/nonnull.7 b/nonnull.7
index 1390b2d..75370e1 100644
--- a/nonnull.7
+++ b/nonnull.7
@@ -44,7 +44,10 @@ through the use of
an lvalue with
.BR \%nonnull -qualified
type,
-the behavior is undefined.
+the behavior is undefined,
+except if preceeding code
+can prove at compile time that the pointer will not possibly be
+.BR NULL .
.PP
The intended use of the
.B \%nonnull
@@ -212,6 +215,32 @@ since the user isn't properly informed that
may cause undefined behavior in the implementation of the function
.RI ( dest
is dereferenced).
+.SS non-nonnull-qualified to nonnull-qualified valid assignment
+.EX
+int *nonnull foo(int *p)
+{
+ if (!p)
+ exit(EXIT_FAILURE);

lvalue-to-rvalue conversion loses qualifiers, which makes any rules based
on whether the RHS of an assignment was nonnull-qualified very
problematic. (The specification of restrict is exceedingly tricky and
very unlikely to be a good basis for specifying any other feature.)

I don't think a manpage is a good form for proposing a language feature.
Actual proposed normative wording for the C standard, showing all relevant
changes to all relevant subclauses, is better.

Hi Joseph,

lvalue-to-rvalue conversion loses qualifiers, which makes any rules based
on whether the RHS of an assignment was nonnull-qualified very
problematic. (The specification of restrict is exceedingly tricky and
very unlikely to be a good basis for specifying any other feature.)

Hmm.
restrict was the closest thing to a
const-like level of safety that I could think of.
It would allow a compiler to keep track of nullness
of every pointer,
and issue appropriate diagnostics
probably better than what -fanalyzer already does.

How is restrict handling that problem of lvalue-to-rvalue already?

Can you think of any other way nonnull-ness could be passed
to nested function calls with language enforcement?

The other option would be to propose plain [[gnu::nonnull]],
which couldn't be enforced across nested function calls
(or I could't think of how yet).
Well, the simplest cases (i.e., not pointer-to-pointer)
could be detected by the compiler,
but other than that, it's impossible.
But if that's the only way,
it's better than nothing.

I don't think a manpage is a good form for proposing a language feature.
Actual proposed normative wording for the C standard, showing all relevant
changes to all relevant subclauses, is better.

My intention is that the final PDF to be sent to the committee
will have those diffs.
But I have no clue of how to do that kind of things,
so for an initial draft to discuss on,
before even presenting it to the committee,
I think my "native" language for writing technical documents
will be easier.

Also,
I'm curious,
do you do those diffs usually by hand?
I mean, you can't diff(1) a PDF, can you? :slight_smile:

Considering that C moves at a 10-years pace,
and we're late for C2X,
I have until around 2030 to learn how to do that :slight_smile:

Thanks!
and kind regards,
Alex

restrict has tricky rules about "based on" (6.7.3.1).

Hi Joseph,

"based on" is about optimizations; I think it's even less suited to
anything relating to diagnostics than it is to optimization.

To restrict assignment between different kinds of pointers, I'd think
you'd want pointer type variants that differ in some way *other* than
qualifiers, a way that's unaffected by lvalue-to-rvalue conversion, but
that comes with its own rules on implicit conversion as if by assignment
(6.5.16.1) (though then you also need to work out what's allowed in terms
of mixing these pointer type variants in all the other operations allowing
pointers, what type results of pointer arithmetic have, etc.). And there
should surely also be some way of converting a normal pointer to this
variant with a runtime check for NULL.

Note that discussion of prior art in such a proposal should also consider
relevant prior art (for constraining possible values of a variable through
the type system) in C++ or other languages if possible.

My intention is that the final PDF to be sent to the committee
will have those diffs.
But I have no clue of how to do that kind of things,
so for an initial draft to discuss on,
before even presenting it to the committee,
I think my “native” language for writing technical documents
will be easier.

Also,
I’m curious,
do you do those diffs usually by hand?

Yes. Just highlight text in red and green, with strike through or underlining.

If you write the paper in LaTeX you can use macros like:

\definecolor{addclr}{rgb}{0,.6,.6}
\definecolor{remclr}{rgb}{1,0,0}

\renewcommand{\added}[1]{\textcolor{addclr}{\uline{#1}}}
\newcommand{\removed}[1]{\textcolor{remclr}{\sout{#1}}}
\renewcommand{\changed}[2]{\removed{#1}\added{#2}}

I mean, you can’t diff(1) a PDF, can you? :slight_smile:

Aside: see diffpdf, which is packaged by some distros.

Hi Joseph,

Hi Joseph,

How is restrict handling that problem of lvalue-to-rvalue already?

restrict has tricky rules about "based on" (6.7.3.1).

Hmm, I think I can "base on" that,
to define what I had in mind. :slight_smile:

"based on" is about optimizations; I think it's even less suited to
anything relating to diagnostics than it is to optimization.

To restrict assignment between different kinds of pointers, I'd think
you'd want pointer type variants that differ in some way *other* than
qualifiers, a way that's unaffected by lvalue-to-rvalue conversion, but
that comes with its own rules on implicit conversion as if by assignment
(6.5.16.1) (though then you also need to work out what's allowed in terms
of mixing these pointer type variants in all the other operations allowing
pointers, what type results of pointer arithmetic have, etc.). And there
should surely also be some way of converting a normal pointer to this
variant with a runtime check for NULL.

Note that discussion of prior art in such a proposal should also consider
relevant prior art (for constraining possible values of a variable through
the type system) in C++ or other languages if possible.

The only other language that I know is C++, so I'll talk about it:

C++ added long ago something close to this: references.
However, IMO:
- They don't provide any enforced safety at all.
   It's just as safe as using [[gnu::nonnull]].
   See an example code below showing why.
- They unnecessarily changed pointer syntax to use value syntax.
   This syntax has not been widely accepted by some C programmers,
   which would have to adapt their minds considerably.

Clang's _Nonnull (and _Nullable) qualifiers (are they?):
- Do have the safety that C++ references should have had,
   if they had been properly designed.
   Again, see the example code below.
- They have an intuitive syntax for C programmers,
   by just adding a qualifier to a pointer,
   you mark it as either possibly being NULL or not.

It has the appearance of a qualifier,
and the documentation refers to it as a qualifier,
but how does it implement it clang internally?
Is it implemented as a completely different type
with some implicit conversion rules?
I don't know.
See
<Attributes in Clang — Clang 16.0.0git documentation.

There is even a tool called latexdiff that tries to automate this. Handy
for dealing with by-law changes and the like :slight_smile:

Joerg

Or, if you use Bikeshed, it’s just the HTML tags and . For example:
https://github.com/Quuxplusone/draft/blob/29d1609da4182504dec7ada25b0700246bb7d86a/Makefile#L12

https://github.com/Quuxplusone/draft/blob/29d1609da4182504dec7ada25b0700246bb7d86a/d2266-implicit-move-rvalue-ref.bs#L529-L552

produces
https://quuxplusone.github.io/draft/d2266-implicit-move-rvalue-ref.html#wording

Even with Bikeshed, copy-pasting the standard text and marking up the diff is still a manual process; but it looks pretty nice IMO, at both the source level and the finished-product level.

HTH,
Arthur

Hi,

Sorry for Clang people,
when I started this thread,
I wasn't subscribed to your list,
and some messages are not on your list.
You can find the complete thread on the GCC list:
<https://gcc.gnu.org/pipermail/gcc/2021-November/237743.html&gt;

I have a few questions for you.
See below, please.

$ cat _Nonnull.c
#include <stdlib.h>

int *_Nonnull f(int *_Nullable p)
{
if (!p)
exit(1);
return p;
}

- I get a warning from f().
Ideally,
a programmer should not need to cast
(casts are dangerous),
to convert a nullable pointer to a _Nonnull pointer.
For that,
appropriate checks should be in the preceeding code.
Otherwise, a diagnostic should be issued.
To be on the safe side,
if a compiler has doubts,
it should diagnose.

There's some Clang document that talks about something similar.
I don't know its validity,
or if it was a draft before _Nonnull qualifiers.
<https://clang.llvm.org/docs/analyzer/developer-docs/nullability.html&gt;

That document suggests that I shouldn't get a diagnostic from f().
Why did I get a diagnostic? (I tried clang 11, 13 & 14(experimental))

Is it talking about a different nonnull attribute/qualifier?
Was it about a proposal prior to the current _Nonnull?
Why is it not in use? Was it too difficult to implement?

Do you think Clang could be improved to not warn on f()?

Thanks,
Alex

Ping

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.

_Null_unspecified is a way to say that nullability is complicated due
to legacy reasons (for example, a function may return NULL under
extremely rare circumstances that most users don't care about, so we
want to allow returning NULL, while suppressing warnings at the usage
site if the user assumes that the returned pointer is non-NULL). It
might have been useful for bringing certain legacy APIs into the
annotated world with more checks enabled. But right now it is used
extremely rarely in Apple's SDKs and therefore it is unclear to me
personally whether it really pulls its weight at this point.

Dmitri

Hi Alejandro,

> $ cat _Nonnull.c
> #include <stdlib.h>
>
> int *_Nonnull f(int *_Nullable p)
> {
> if (!p)
> exit(1);
> return p;
> }
>
>
> - I get a warning from f().
> Ideally,
> a programmer should not need to cast
> (casts are dangerous),
> to convert a nullable pointer to a _Nonnull pointer.
> For that,
> appropriate checks should be in the preceeding code.
> Otherwise, a diagnostic should be issued.
> To be on the safe side,
> if a compiler has doubts,
> it should diagnose.
>
> There's some Clang document that talks about something similar.
> I don't know its validity,
> or if it was a draft before _Nonnull qualifiers.
> <https://clang.llvm.org/docs/analyzer/developer-docs/nullability.html&gt;

That document suggests that I shouldn't get a diagnostic from f().
Why did I get a diagnostic? (I tried clang 11, 13 & 14(experimental))

Is it talking about a different nonnull attribute/qualifier?
Was it about a proposal prior to the current _Nonnull?
Why is it not in use? Was it too difficult to implement?

The false positive you're getting is from the Clang warning
-Wnullable-to-nonnull-conversion. It is a simple type-based check that
does not take dataflow information into account. In other words, the
reason for the nullability false positive in your example is identical
to the reason for the integer conversion false positive here:

#include <stdlib.h>
#include <stdint.h>
#include <limits.h>

uint8_t f(uint32_t x) {
      if (x > UINT8_MAX)
          exit(1);
     return x;
}

warning: implicit conversion loses integer precision: 'uint32_t' (aka
'unsigned int') to 'uint8_t' (aka 'unsigned char')
[-Wimplicit-int-conversion]

This webpage 3.4. Nullability Checks — Clang 16.0.0git documentation
describes Clang Static Analyzer, is a sophisticated path-sensitive
static analysis tool. It is unfortunately often too slow to enable in
regular compilation.

Do you think Clang could be improved to not warn on f()?

Absolutely. We can implement a dataflow-based check that takes the
flow condition into account. Flow-sensitive diagnostics should scale a
lot better than path-sensitive, and they should be fast enough to
become a compiler warning. We are currently upstreaming a dataflow
analysis framework that should make implementing such diagnostics
easier. [cfe-dev] [RFC] A dataflow analysis framework for Clang AST

Dmitri

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.

Old code will stop compiling if it uses NULL, but that can easily be fixed by marking the pointers as _Nullable, and maybe while at that, programmers will find a few bugs.

Compilers will have to be carefull, because memcpy() will make NULL members of structures, so they'll need to know if that can be done or not, and many structure members will need to be marked as _Nullable, if the structure is expected to be bzero()ed.

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;

Thanks,
Alex

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 ([cfe-dev] [RFC] A dataflow analysis framework for Clang AST)
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

Have you read the old discussions on this topic? Without wanting to
sound harsh, I'm missing the justification for why the optimisation is
useful. The clang _Nonnull exists exactly because the GCC attribute has
proven to be much more harmful than it ever helped to produce better
code. Your proposal should therefore at the very least discuss two
related topics:

(1) Why is it a good idea to remove explicit sanity checks from the
code? History of many libraries have proven that this checks are much
more useful than the branching cost and it is very surprising if the
compiler drops them.

(2) Why can propagation of assumptions not result in the
removal of range checks? See the whole deferencing-implies-notnull
discussion from the Linux kernel a few years ago for why this can be
problematic.

As it stands, this just seems to repeat the mistakes of the GCC
attributes in a slightly different shape.

Joerg

Hi Joerg,

       Clang doesn't specify the behavior as being undefined.
       That forbids optimizations, that would otherwise be pos‐
       sible. We prefer to allow for those optimizations.

Have you read the old discussions on this topic? Without wanting to
sound harsh, I'm missing the justification for why the optimisation is
useful. The clang _Nonnull exists exactly because the GCC attribute has
proven to be much more harmful than it ever helped to produce better
code. Your proposal should therefore at the very least discuss two
related topics:

(1) Why is it a good idea to remove explicit sanity checks from the
code? History of many libraries have proven that this checks are much
more useful than the branching cost and it is very surprising if the
compiler drops them.

(2) Why can propagation of assumptions not result in the
removal of range checks? See the whole deferencing-implies-notnull
discussion from the Linux kernel a few years ago for why this can be
problematic.

As it stands, this just seems to repeat the mistakes of the GCC
attributes in a slightly different shape.

I think my wording was incorrect. And after having thought about some
complex situations with _Nonnull (i.e., don't warn about
nonnull-to-nullable assignment if nonnull-ness can be proven), I think
UB should be out of the table. The more I play with Clang's _Nonnull,
the more I'm convinced that it's exactly what C3X should add. I didn't
reply to this thread in a long time because I was testing it.

I've read some of the discussions about nonnull, but not many others.
If someone can provide links to them, it'd be great. So, thanks in advance.

So,

(1): No. Explicit sanity checks should never be removed by the
compiler. Instead it should warn about them being unnecessary, and let
the user remove them. If the user removes the checks, the resulting
optimization should be the same as if the compiler had done so, plus the
user has the opportunity to ignore warnings, as always, and keep the
checks if for some weird reason they are needed.

(2): I'm not sure I understand this one. I also didn't find the LKML
thread. My idea is that if the compiler enforces nonnull-ness as it
does currently with const, it will be possible to guarantee that sanity
checks are unnecessary, and therefore they can be safely omitted (by the
user, not the compiler).

The idea is making it impossible to pass a null pointer to a _Nonnull
pointer, so if I declare a parameter as _Nonnull, the sanity checks are
unnecessary. Only ignoring warnings (this should go into -Wall) could
then produce UB.

Cheers,
Alex

Hi Dmitry,

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.

Agreed. I even found some cases where it would cause previously-correct
code to misbehave, so changing normal pointers to mean nonnull pointers
is not possible at this point. Compilers may allow that with pragmas
anyway.

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.

Yes

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.

Let's avoid forking C :slight_smile:

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 ([cfe-dev] [RFC] A dataflow analysis framework for Clang AST)
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++.

Let's talk about _Nonnull, which is more likely to be added.

I would say no. Neither const or restrict create a new type, but a
qualified version of a type. _Nonnull has a behavior somewhat in
between restrict and const.

- 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`.

I think this should be similar to const, so a mandatory warning would be
better. But still I have doubts, because this is much more complex to
diagnose than const violations (there should be no diagnostic after an
explicit NULL check).

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.

Yes, it's complex. It should be flow-sensitive. Right now there's a
precedent, I think: 'restrict'. But there are no mandatory warnings
AFAIK. This would go one step further, and would probably be the most
complex addition to the language ever. A minimal complying compiler
would need to be much more complex than ever, which is a bad thing.

Right now I have two voices in my head saying opposite things: on one
hand this would add a lot of complexity to the C language, and I don't
like that; on the other hand, this would make standard C much more safe
than it is regarding pointers, and for the plain user, it would reduce
the cognitive complexity of making sure null pointers are not passed
where they shouldn't.

GCC adding _Nonnull before the standard would be a good first step.

Cheers,
Alex