ISO C3X proposal: nonnull qualifier

The original "bug" boils down to something like this:

   int f(int *p) {
      int x = *p;
      if (!p)
        return -1;
      return x;
   }

GCC sees the *p, and drops the if condition. Replace that with a call to
a function that has a nonnull attribute and you get the same problem.

Joerg

Hi Joerg,

(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 original "bug" boils down to something like this:

   int f(int *p) {
      int x = *p;
      if (!p)
        return -1;
      return x;
   }

GCC sees the *p, and drops the if condition. Replace that with a call to
a function that has a nonnull attribute and you get the same problem.

If I add [[gnu::nonnull]], I get a warning (-Wnonnull-compare, implied
by -Wall) with GCC, even with -O3:

nonnull.c: In function ‘f’:
nonnull.c:5:10: warning: ‘nonnull’ argument ‘p’ compared to NULL
[-Wnonnull-compare]
    5 | if (!p)
      > ^

This warning should be mandatory by the standard IMO (if _Nonnull is
added), since there's no valid point in comparing a nonnull pointer to
NULL, but could also be non-mandatory, since it's not a dangerous thing.
Having it in -Wall would be fine too.

(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 original “bug” boils down to something like this:

int f(int *p) {
int x = *p;
if (!p)
return -1;
return x;
}

GCC sees the *p, and drops the if condition. Replace that with a call to
a function that has a nonnull attribute and you get the same problem.

I remember vaguely when this was going on, and have multiple times in the past decade wished there was a complete explainer somewhere with all the original links in one place. (If people send me the links, I’ll do a blog post with all of them! :)) Here are two links I just googled up, from 2009:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a3ca86aea507904148870946d599e07a340b39bf

https://lwn.net/Articles/341773/
Warning: the LWN comments contain vastly more heat than light. The top comment gives a great summary of the “correct” side:

By looking at the source, this is an obvious coding error - kernel first dereferences a pointer, and after that checks whether it’s NULL.

But you’ll see that there are also several people arguing on the other side, that this is obviously a compiler bug:

The exploit works because in this case, null is NOT an invalid value, but the compiled code> behaves as though it is (and as though the compiler knows how the dereferencing of an
invalid value will be handled … which it doesn’t). So yes, it’s a compiler bug.

(Anyway, this is all just for historical perspectives. Note that in a perfect world, the compiler would warn about “null-check after dereference,” and then somehow refrain from optimizing away any null-check unless it had definitely warned about that exact null-check already. Unfortunately, dataflow analysis doesn’t work like that in practice.)

–Arthur

1 Like

The issue is this code comes up in many non-broken cases, like the case Joerg mentions once you indirect through a function call:

bool other_func(int p) {
if (!p)
return true;
/
probably do some other stuff */
}
int f(int *p) {
int x = *p;
if (other_func(p))
return -1;
return x;
}

Now it’s not clear this code isn’t working as intended - it’s probably possible to say that for every case Clang can warn about (ones that don’t require a lot of non-local reasoning) it could also suppress any optimization of such a null check (but would that be especially useful? I find it doubtful - the user could address the warning instead), but for all the more complicated non-local reasoning (due to inlining or other optimizations) it’s unlikely Clang would suppress such optimizations - because in the above code, for instance, the optimization may be exactly what the user wants - in this /particular/ call site of other_func, the pointer is always non-null so the compiler can optimize away the null test from some generic code when specializing it for this particular call site.

  • Dave

Hi David,

The issue is this code comes up in many non-broken cases, like the case
Joerg mentions once you indirect through a function call:

bool other_func(int *p) {
if (!p)
return true;
/* probably do some other stuff */
}
int f(int *p) {
int x = *p;
if (other_func(p))
return -1;
return x;
}

Now it's not clear this code isn't working as intended - it's probably
possible to say that for every case Clang can warn about (ones that
don't require a lot of non-local reasoning) it could also suppress any
optimization of such a null check (but would that be especially useful?
I find it doubtful - the user could address the warning instead), but
for all the more complicated non-local reasoning (due to inlining or
other optimizations) it's unlikely Clang would suppress such
optimizations - because in the above code, for instance, the
optimization may be exactly what the user wants - in this /particular/
call site of other_func, the pointer is always non-null so the compiler
can optimize away the null test from some generic code when specializing
it for this particular call site.

In this case it seems correct code to me. A _Nonnull qualifier would
better clarify the correctness/intent of the code:

   bool other_func(int *p) {
     if (!p)
       return true;
     /* probably do some other stuff */
   }
   int f(int *_Nonnull p) {
       int x = *p;
       if (other_func(p))
         return -1;
       return x;
   }

The above would be fine, and the optimization would be perfect. Since
there's an implicit conversion where the _Nonnull qualifier is
discarded, there should be no warning, since the check is done to a
nullable pointer.

   bool other_func(int *p) {
     if (!p)
       return true;
     /* probably do some other stuff */
   }
   int f(int *p) {
       int x = *p;
       if (other_func(p))
         return -1;
       return x;
   }

The above code, in an ideal C3X scenario, would issue a warning at
`int x = *p;`
due to dereference of a nullable pointer. Now that I think, since this
was legal C in the past, this warning should not be mandatory (otherwise
we would issue many spurious warnings for old code), but could be warned
by -Wall.

   bool other_func(int *_Nonnull p) {
     if (!p)
       return true;
     /* probably do some other stuff */
   }
   int f(int *_Nonnull p) {
       int x = *p;
       if (other_func(p))
         return -1;
       return x;
   }

And this code should have a mandatory warning: comparing to NULL a
nonnull pointer.

   bool other_func(int *_Nonnull p) {
     if (!p)
       return true;
     /* probably do some other stuff */
   }
   int f(int *p) {
       int x = *p;
       if (other_func(p))
         return -1;
       return x;
   }

Of course, ignoring the dereference, this code would also have another
warning (this one could be mandatory): Passing p to other_func()
implicitly adds _Nonnull qualifier.

Cheers,

Alex

Hi David,

Macros can also produce code that may introduce NULL checks for nonnull
pointers, and these will be trickier, since there's no discarding of the
qualifier, so... maybe a warning is more difficult to get right. But
since the optimization is not dangerous if the rest of the code
correctly propagates the _Nonnull-ness of pointers, that shouldn't be a
problem.

If a function (or a block) uses a _Nonnull pointer, then the compiler
can optimize as much as it want there. It's at the point of assigning
that pointer (normally at function boundaries) where the danger is. If
you make it impossible to assign a nullable to a nonnull (with the
exception of a preceeding NULL check), then the whole chain is safe.

However, I'd like to do many more tests before coming with a revision of
my proposal.

Cheers,
Alex

Hi Alejandro,

Hi Dmitri

$ 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;
}

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

Getting warnings from h() and g() would be easy to impose. Just forbid implicit addition of _Nonnull qualifier.

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

I came with an idea that would make this QoI, so that the ISO standard wouldn't need to force every compiler to be flow-sensitive, which would be a bad precedent.

Implicit additions of the qualifier could be allowed to be warned _always_ by the standard.

As an extension, quality compilers may not warn when they can see a NULL check just before the assignment.

User code could make of the following macro, to avoid having to add casts everywhere, which would be as bad as not having _Nonnull at all. I got the idea from the GCC __builtin_xxx_overflow() builtins.

  #if defined(__cplusplus)
  #define const_cast(t, x) const_cast<t>(x)
  #else
  #define const_cast(t, x) ((t) (x))
  #endif

  #if !defined(cplusplus)
  #define auto __auto_type
  #endif

  #define nonnull_assign(nn, p) \
  ({ \
    auto p_ = p; \
    auto nn_ = nn; \
                                                               \
    if (p_ == NULL) \
      *nn_ = const_cast(typeof(nn_), p_); \
                                                               \
    p_ == NULL; \
  })

And use it like this:

  int *_Nonnull f(int *_Nullable p)
  {
    int *_Nonnull q;

    if (nonnull_assign(&q, p))
      exit(1);
    return q;
  }

That way there's only one place where there's a cast, so it can be easily audited. The above would be warning-free, without requiring a lot of complexity into the compiler.

Please, forgive the extensive use of GNU extensions in the above snippet (and the redefinition of auto), as I forgive those who don't use them :slight_smile:

So, now this can be made non-flow-sensitive, which was a big concern. And now the biggest concern I can see is that this qualifier works opposite to const (here discarding is allowed but not adding it), and that is contrary to how compilers have been working for now. As far as I could read in the standard, there's no mention to qualifiers being dropped in an rvalue; Joseph, could you please confirm? Also, as I already mentioned, Clang already implements this somehow without discarding the _Nonnull qualifier, so I guess this can be done.

Cheers,
Alex

 \#define nonnull\_assign\(nn, p\)                         \\
 \(\{                                                    \\
     auto p\_  = p;                                 \\
     auto nn\_ = nn;                                \\
                                                           \\
     if \(p\_ == NULL\)                               \\
         \*nn\_ = const\_cast\(typeof\(nn\_\), p\_\);  \\

D'oh, this check should be the opposite, of course :slight_smile:

                                                           \\
     p\_ == NULL;                                   \\
 \}\)

And I forgot to mention, that this macro intentionally leaves the nonnull pointer (nn) uninitialized in the case of a NULL input pointer (p), so that the compiler can then warn about an uninitialized variable if it's used uninitialized.

Cheers,
Alex

So, now this can be made non-flow-sensitive, which was a big concern. And now
the biggest concern I can see is that this qualifier works opposite to const
(here discarding is allowed but not adding it), and that is contrary to how

For all existing qualifiers, the rules about discarding are rules about
permitted assignments (and conversions as if by assignment) between
pointers and concern the qualifiers on pointer target types: 6.5.16.1 is
the key subclause concerning implicit conversions, and any proposal for
changes in that area needs to be precise about exactly what textual
changes are proposed to 6.5.16.1.

compilers have been working for now. As far as I could read in the standard,
there's no mention to qualifiers being dropped in an rvalue; Joseph, could you
please confirm? Also, as I already mentioned, Clang already implements this

lvalue-to-rvalue conversion drops qualifiers (and _Atomic). "If the
lvalue has qualified type, the value has the unqualified version of the
type of the lvalue; additionally, if the lvalue has atomic type, the value
has the non-atomic version of the type of the lvalue; otherwise, the value
has the type of the lvalue." (6.3.2.1).

Hi,

I love C programming (I’ve been doing it for 23 years) and I think something like your idea would be a great addition to the language. I was already thinking about this topic extensively since Friday, including all the possible ways to introduce ‘null’/‘non-null’ type qualifiers.

It seems to me that the only way of adding nullability information to C’s type system that is sympathetic to the language’s syntax, and allows the information to be added to programs gradually without breaking compatibility, would be to create a pointer target type qualifier like ‘const’. Putting the type qualifier on the pointer type (like ‘restrict’) is just wrong IMO.

My idea is simply to add a new pointer target type qualifier, analogous to 'const’​, except that the new qualifier would mean “the address of this object may be a null pointer”. It would have the same syntax and usage rules as 'const’​ (unlike, say, C++ references):

int optional *x = NULL;
int *y = x; // compiler error
int optional *z = x; // okay

That would allow people to annotate which parts of their code can safely handle null pointer values, in the same way that they can already annotate which parts of their code can safely handle addresses of immutable objects. I think this idea is equally applicable to C and C++, and wouldn’t upset people who dislike C++ syntax, nor fall into the trap of “implementing C++ in C”.

I thought the new qualifier could be called ‘optional’ by analogy with the Python type annotation of the same name. This avoids clashing with the names of existing Clang annotations such as _Nullable ​ or _Nonnull ​ which aren’t part of the language’s type system and seem only to be understood by Clang-format. (Those annotations are also less strict and more complex than what I envisaged.)

My choice of name also puts mental space between pointer type qualifiers that are implicitly discarded on copy (such as ‘restrict’) and my proposed pointer target type qualifier, which cannot be discarded on copy (like 'const’​). In other words, it’s a statement about the object, not the address of the object:

int optional q = 4;
int r = q; // okay

Since optional isn’t a reserved word, it would need to be defined as a macro alias for an inbuilt name like _Optional`​ in a header file (e.g. a header named <stdopt.h> ​ by analogy with <stdbool.h>​)

Existing code could easily be updated progressively​:

  • functions which consume pointers that can be null could be changed with no effect on compatibility. For example, void free(void optional *) ​ could consume a pointer to an optional​-qualified type, or a pointer to an unqualified type, without casting.
  • ‘Safe’ variants of functions which produce pointers could also be written, e.g.

void optional *malloc(size_t);

would produce a pointer that can only be passed to functions which accept optional-qualified pointer target types. Crucially, however, this is not necessary! Assigning the return value of standard malloc() ​ to an object of any pointer-to-optional type would be sufficient to ensure that it cannot be used with any interface (or local variable) which doesn’t promise to handle a null value safely.

I don’t envisage that a language standard would mandate that compilers statically check for unchecked dereferences of pointers to ‘optional’ types, but they would acquire the ability to do:

extern int optional *z;
if (z != NULL) ++z; // okay (based on usage)
++z; // optional compiler warning (based on usage)

Hi Christopher,

[Christopher_Bazley] Christopher_Bazley
https://discourse.llvm.org/u/christopher_bazley
August 4

Hi,

I love C programming (I’ve been doing it for 23 years) and I think
something like your idea would be a great addition to the language. I
was already thinking about this topic extensively since Friday,
including all the possible ways to introduce ‘null’/‘non-null’ type
qualifiers.

It seems to me that the only way of adding nullability information to
C’s type system that is sympathetic to the language’s syntax, and allows
the information to be added to programs gradually without breaking
compatibility, would be to create a pointer /target/ type qualifier like
‘const’. Putting the type qualifier on the pointer type (like
‘restrict’) is just wrong IMO.

Yes… more or less.

I don’t like the idea of something like restrict at all. At the same
time, it’s the only way that we can guarantee 100% backwards
compatibility. But I prefer not doing it, rather than doing it bad, so
I’d completely discard the idea of something like restrict (i.e., I
discard the idea of _Nonnull and [[gnu::nonnull]]).

My idea is simply to add a new pointer target type qualifier, analogous
to 'const’​, except that the new qualifier would mean “the address of
this object may be a null pointer”. It would have the same syntax and
usage rules as 'const’​ (unlike, say, C++ references):

int optional *x = NULL;
int *y = x; // compiler error
int optional *z = x; // okay

I agree.

This is more or less Clang’s _Nullable, if Clang wanted to enforce it
(it perfectly could).

But your idea of moving it down to the type instead of the pointer might
makes sense, to avoid the issue of dropping qualifiers in
lvalue-to-rvalue conversions.

A small problem is that it has issues with backwards compatibility:

[
// This would be the new definition of NULL:
#define NULL ((void optional *)0)

int *p = NULL; // Compiler error; Valid in current ISO C
int optional *q = NULL; // Valid
]

We’re making ‘p’ be a compile error, so code would have to be updated to
work with C3x; it wouldn’t work out-of-the-box. So, if you pick current
C code and try to compile it with a compiler that has this feature, it
would stop compiling. Of course, the compiler could issue just a
warning, and let compilation continue, and let the programmer adapt to
this feature slowly.

In the end, I think it’s a good move. I wasn’t at the time ‘const’ was
introduced, but I guess it was more or less the same pain, and we have a
net gain some years after, when everyone supports const.

However, there’s still something that needs to be thought out:

[
int foo(int optional *p, int a)
{
if (p != NULL)
return *p;
return a;
}
]

I want the code above to work. But of course, I’m not allowed to
dereference a pointer to optional, am I? How will the compiler let me
do that? I still needs to know the paths of code previous to the
dereference, to deduct that I checked for nullness.

[…]

I thought the new qualifier could be called ‘optional’ by analogy with
the Python type annotation of the same name. This avoids clashing with
the names of existing Clang annotations such as _Nullable ​ or _Nonnull
​ which aren’t part of the language’s type system and seem only to be
understood by Clang-format. (Those annotations are also less strict and
more complex than what I envisaged.)

I don’t know the py optional keyword. Can you link to it? It makes
sense to me the name.

My choice of name also puts mental space between pointer type qualifiers
that are implicitly discarded on copy (such as ‘restrict’) and my
proposed pointer /target/ type qualifier, which cannot be discarded on
copy (like 'const’​). In other words, it’s a statement about the object,
not the address of the object:

int optional q = 4;
int r = q; // okay

Yes, makes sense.

Since optional isn’t a reserved word, it would need to be defined as a
macro alias for an inbuilt name like _Optional`​ in a header file (e.g.
a header named <stdopt.h> ​ by analogy with <stdbool.h>​)

Also makes sense.

Existing code could easily be updated progressively​:

  • functions which consume pointers that can be null could be changed
    with no effect on compatibility. For example, void free(void
    optional *) ​ could consume a pointer to an optional​-qualified
    type, or a pointer to an unqualified type, without casting.
  • ‘Safe’ variants of functions which produce pointers could also be
    written, e.g.

void optional *malloc(size_t);

would produce a pointer that can only be passed to functions which
accept optional-qualified pointer target types. Crucially, however, this
is not necessary! Assigning the return value of standard malloc() ​ to
an object of any pointer-to-optional type would be sufficient to ensure
that it cannot be used with any interface (or local variable) which
doesn’t promise to handle a null value safely.

I don’t envisage that a language standard would mandate that compilers
statically check for unchecked dereferences of pointers to ‘optional’
types, but they would acquire the ability to do:

extern int optional *z;
if (z != NULL) ++z; // okay (based on usage)
++z; // optional compiler warning (based on usage)

No, as discussed above, it probably shouldn’t, as it doesn’t with const.

Cheers,

Alex

It wasn’t my intention to propose changing the definition of the NULL macro. Apart from anything else, people might not even use that macro, and as your observed, changing its definition to incorporate the new optional qualifier would break all existing code.

It would be better to add a new macro incorporating that qualifier, if desired.

I assume that you will be able to dereference a pointer to optional, and it will have the same (undefined) behaviour as dereferencing any other pointer if the value is null. The difference is that the compiler will gain the ability to warn about missing null checks before an optional pointer is dereferenced.

The alternative of requiring the pointer type to be explicitly converted to remove the optional qualifier before dereferencing it seems too annoying (and not type-safe either):

int foo(int optional *p, int a)
{
if (p != NULL)
return *(int *)p;
return a;
}

Sorry, I’m not sure I understood you correctly. Are you saying that compilers should be banned from complaining about dereferences of pointers to optional that haven’t been guarded by NULL checks? I don’t think that’s a good idea.

There’s a big difference between mandating such compile-time checking in the C standard and forbidding it. The issue with the C language as it currently exists is that it’s impossible to do such checking without additional type annotations, because null is not a legitimate value for all pointers.

The main issues my colleagues raised with my proposal are these:

  1. The compiler isn’t required to refuse to compile the following code (or emit a diagnostic message):
void do_something(int optional *i)
{
    printf("%i", *i);
}
  1. A cast is required to cast away the new type qualifier if checks for null aren’t in the same function as a null pointer dereference:
void do_another_thing(int *i);
void do_something(int optional *i)
{
    if (i != NULL) {
      do_another_thing((int *)i);
    }
}

This cast isn’t type-safe, so it could be argued that it is actually making the code worse.

One answer to the second point is that null checking could/should be put in leaf functions by preference, but there are many common functions that won’t accept a pointer to an optional object (such as strlen) that a function might want to call after checking for a non-null pointer.

A safer way of casting away pointer target type qualifiers would help, e.g.

#include <assert.h>

#define DECL_OPTIONAL_CAST(type) \
static inline type *optional_cast_ ## type(type optional *x) \
{ \
  assert(x); \
  return (type *)x; \
}
DECL_OPTIONAL_CAST(double)
DECL_OPTIONAL_CAST(float)
DECL_OPTIONAL_CAST(char)
DECL_OPTIONAL_CAST(int)

#define optional_cast(X) _Generic((*X), \
              double :optional_cast_double, \
              float: optional_cast_float, \
              char: optional_cast_char, \
              int: optional_cast_int \
              )(X)

int main(void)
{
    double k = 9;
    double optional *i = &k;
    double *j = optional_cast(i);
    return *j;
}

However that won’t work for user-defined types. A built-in compiler function to do the casting would solve that issue, but that is straying into C++ territory.

A more interesting (and very clever) solution proposed by one of my colleagues was to change the semantics of taking the address of an optional-qualified type:

int optional *x;
int *y = x; // error
int *z = &*x; // okay (optional diagnostic if compiler cannot infer locally that x is not null)
int optional r;
int *s = &r; // okay

This behaviour is initially surprising, but it does kind of make sense that the address of an optional object is not a pointer-to-optional , because null is not the address of any object, by definition.

With this rule, writing &*x would be a type-safe equivalent to (int *) (or (char *) or whatever) for removing an optional qualifier from a pointer target type.

Sure. I was thinking of this: typing — Support for type hints — Python 3.12.0 documentation and Kinds of types - mypy 1.7.1 documentation

Incidentally, this would be a change from the semantics of &*x for const or volatile qualifiers (and possibly therefore rejected). The desired behaviour is more like an initializer list y = *x, *z = &y; where x is pointer to optional, y is an intermediate object and z is the desired unqualified pointer:

    double k = 9;
    double const *i = &k;
    double *a = &*i; // initializing 'double *' with an expression of type 'const double *' discards qualifiers
    double b = *i, *c = &b;

Hi Christopher,

Could you please send plain text via email? It’s hard to read HTML email.

Alejandro_Colomar:

A small problem is that it has issues with backwards compatibility:

[
// This would be the new definition of NULL:
#define NULL ((void optional *)0)

int *p = NULL; // Compiler error; Valid in current ISO C
int optional *q = NULL; // Valid
]

We’re making ‘p’ be a compile error, so code would have to be updated to
work with C3x; it wouldn’t work out-of-the-box. So, if you pick current
C code and try to compile it with a compiler that has this feature, it
would stop compiling. Of course, the compiler could issue just a
warning, and let compilation continue, and let the programmer adapt to
this feature slowly.

It wasn’t my intention to propose changing the definition of the NULL
macro. Apart from anything else, people might not even use that macro,
and as your observed, changing its definition to incorporate the new

optional> qualifier would break all existing code.

We definitely want to redefine NULL if we’re adding this feature.
Otherwise, what is the purpose of this feature?

Checking that a pointer doesn’t contain a null pointer, but NULL is not
considered a null pointer? NULL definitely needs to be defined as:

#define NULL ((void _Optional *)0)

Defining yet another null pointer name, as C++ did with nullptr, is just
confusing, and provides no benefit. C++ did a big mistake in creating
nullptr, when they could have perfectly redefined NULL to be
‘((nullptr_t)0)’ (and I can’t see a situation where that would have
failed). I don’t want C to repeat those mistakes.

It would be better to add a new macro incorporating that qualifier, if
desired.

No.

Otherwise, are you saying that this should be fine for a compiler?:

memcpy(NULL, str, 10);

We are adding _Optional exactly to warn about that.

Cheers,

Alex

Hi Joseph,

Alejandro_Colomar:

However, there’s still something that needs to be thought out:

[
int foo(int optional *p, int a)
{
if (p != NULL)
return *p;
return a;
}
]

I want the code above to work. But of course, I’m not allowed to
dereference a pointer to optional, am I? How will the compiler let me
do that? I still needs to know the paths of code previous to the
dereference, to deduct that I checked for nullness.

I assume that you will be able to dereference a pointer to |optional|,
and it will have the same (undefined) behaviour as dereferencing any
other pointer if the value is null. The difference is that the compiler
will gain the ability to warn about missing null checks before an

optional> pointer is dereferenced.

Joseph, could a compiler warn about such cases with no false positives
or negatives? If so, this can make sense. It seems that theoretically
it can. But are there issues for implementing this?

The problem is that this is not so black-or-white as with const, where
you can’t assign a const t* to a t*. In this case, you can still assign
(with no warning, I mean), if you have a previous NULL check.

The alternative of requiring the pointer type to be explicitly converted
to remove the |optional| qualifier before dereferencing it seems too
annoying (and not type-safe either):
[
int foo(int optional *p, int a)
{
if (p != NULL)
return *(int *)p;
return a;
}
]

This is clearly a deal breaker. Casts are worse than the status-quo.

Cheers,

Alex

Hi Christopher,

[Christopher_Bazley] Christopher_Bazley
https://discourse.llvm.org/u/christopher_bazley
August 4

The main issues my colleagues raised with my proposal are these:

  1. The compiler isn’t required to refuse to compile the following code
    (or emit a diagnostic message):

void do_something(int optional *i) { (*i)++; } |

The compiler is neither required to refuse the following:

void modify(const int *p) {*p = 42;}

So this is not an issue. The thing is that if you enable -Woptional (or
whatever we call this), it should warn about it.

  1. A cast is required to cast away the new type qualifier if checks for
    null aren’t in the same function as a null pointer dereference:

void do_another_thing(int *i); void do_something(int optional *i) { if
(i != NULL) { do_another_thing((int *)i); } } |

No. The compiler should convert the (int optional *) to (int *) without
a warning, since it sees a preceeding NULL check in scope where the
automatic conversion is being done. A cast should not be needed.

This cast isn’t type-safe, so it could be argued that it is actually
making the code worse. >
One answer to the second point is that null checking could/should be put
in leaf functions by preference, but there are many common functions
that won’t accept a pointer to an |optional| object (such as |strlen|)
that a function might want to call after checking for a non-null pointer.

A safer way of casting away pointer target type qualifiers would help, e.g.

[…]

No. No casts. Not even C+±like casts. That completely defeats the
purpose of the feature. This should be done by automatic conversions,
allowed by the compiler iff there’s a preceeding NULL check.

Cheers,

Alex