3 ms·
I recall a bug-report discussion that I sadly have never been able to find. It contains a pretty bad side-effect of this. It had code like: int *p; //
by rocqua 5y ago
I recall a bug-report discussion that I sadly have never been able to find. It contains a pretty bad side-effect of this.
It had code like:
int *p;
// lots of code
if (p != NULL)
return 1;
// use p
Then a later refactor wrongly added a single line before the if statement:
int *p;
// lots of code
int a = *p;
if (p != NULL)
return 1;
// use p
This meant the null check was optimized away, since de referencing a null pointer is undefined behavior, so the if-statement can be assumed to be always false.
This then lead to actual errors (perhaps even an exploit, I do not recall) arising from the removed null-check.
I think in general the "sanity check" cases are the worst. It is hard to determine whether an expression causes undefined behavior if you cannot try and evaluate it. Perhaps a compiler intrinsic that checks (at runtime) whether an expression causes undefined behavior could be useful here. Though I can imagine such an intrinsic being essentially impossible to implement.
- tom_mellior 5y agoThis was in the Linux kernel, which is compiled with special kernel flags which make dereferencing null pointers legal. In the context of that code, dereferencing a pointer and later checking it for null was absoutely meaningful. Optimizing the later null check was a compiler bug that was acknowledged and fixed. The compiler here didn't respect the semantics it had promised to kernel code. This was entirely uncontroversial; it was not a case of "unwanted optimization based on undefined behavior", it was a case of "compiler bug breaking well-defined code". Again, all this in a kernel context. In user code GCC will still happily remove the null check because in user code this is an actual bug in the user's code.
- nwallin 5y agohttps://lwn.net/Articles/342330/ https://lwn.net/Articles/342330/ https://lwn.net/Articles/342420/ https://lwn.net/Articles/342420/
- a1369209993 5y agoActually, there's a even worse version: struct foo { ...; bar_t bar[NBAR]; }; struct foo* p = ...; bar_t* q = &p->bar[0]; // add rq, rp, #foo_bar_offs // other declarations if(!p) return NOPE; // optimized out // use p and q Not even any dereferencing, just pointer arithmetic.
- schlupa 5y agoYes, that one is especially nasty as &p->bar[0] is a constant expression completely solvable at compile time. It's equivalent to the offsetof() macro of stddef.h
- tom_mellior 5y agoCould you post a complete example please and tell us which compiler optimizes out this check? Clang and GCC don't: https://gcc.godbolt.org/z/dbTadEcra https://gcc.godbolt.org/z/dbTadEcra