Hi Joseph,

On 11/15/21 23:47, Joseph Myers wrote:
On Mon, 15 Nov 2021, Alejandro Colomar (man-pages) via Gcc wrote:

Hi Joseph,

On 11/15/21 23:17, Joseph Myers wrote:
On Mon, 15 Nov 2021, Alejandro Colomar (man-pages) via Gcc wrote:

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. :)

"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
<https://clang.llvm.org/docs/AttributeReference.html#nullability-attributes>.


---

$ cat nonnull.c
[[gnu::returns_nonnull]]
int *foo(int *p)
{
        return p;
}
/*
 * No (nonnull-related) diagnostics from the following commands:
 * $ gcc -Wall -Wextra -std=gnu2x -S nonnull.c
 * $ clang -Weverything -std=gnu2x -S nonnull.c
 */

/*++++++++++++*/

/* Clang only */
int *_Nonnull bar(int *_Nullable p)
{
        return p;
}
/*
 * Clang provides safety here:
 *
 * $ clang -Weverything -std=gnu2x -S nonnull.c
* nonnull.c:17:9: warning: implicit conversion from nullable pointer 'int * _Nullable' to non-nullable pointer type 'int * _Nonnull' [-Wnullable-to-nonnull-conversion]
 *         return p;
 *                ^
 */

/*++++++++++++*/

/* C++ only */
int *baz(int *p)
{
        int &q = *p;
        return &q;
}
/* No (nonnull-related) diagnostics from the following commands:
 * $ gcc -Wall -Wextra -std=gnu++2b -S nonnull.cxx
 * $ clang -Weverything -std=gnu++20 -S nonnull.cxx
 */

---


So we have the following list of prior art:

- [[gnu::nonnull]]  (GCC)
- _Nonnull          (Clang)
- & (references)    (C++)

From which I completely dislike references,
for not adding any real benefits,
and being unnecessarily unintuitive (to C programmers).

I like from _Nonnull that it enforces diagnostics.
And I like from [[gnu::nonnull]] that it allows optimizations.
I'm trying to mix them in a good way.

Since Clang's _Nonnull is closer to what I have in mind,
I did further tests to _Nonnull,
to see what I like,
and what I dislike:

---

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

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

int *_Nonnull g(int *_Null_unspecified p)
{
        return p;
}

int *_Nonnull h(int *p)
{
        return p;
}

int *_Nullable i(int *_Nonnull p)
{
        return p;
}

---

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.

Then,
I see other problems:

- I don't get a warning from g(), nor from h(), which I'd want.
  To get something like const-safety,
  we need to design it so that it can be enforced;
  either you guarantee that a pointer cannot be NULL (_Nonnull),
  or you cannot guarantee it (not _Nonnull).
  Otherwise,
  [[gnu::nonnull]] would seem better to me.

  So, I'd leave out of the design everything but _Nonnull,
  and consider everything else as nullable by default.

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


Thanks!
Alex


Reply via email to