3 ms·
What they do is make assumptions like "null pointers are never dereferenced." So, they write optimizations that assume it doesn't happen. When their UB-obliviou
by trealira 2y ago
What they do is make assumptions like "null pointers are never dereferenced." So, they write optimizations that assume it doesn't happen. When their UB-oblivious optimizer runs into a situation where the user dereferences a pointer, then checks for null later, it basically reasons "The pointer was dereferenced earlier, so this pointer is definitely not null," implicitly assuming that null pointers are never dereferenced. "Therefore, this null check is unnecessary and can be deleted."
This is simplified somewhat, since there are now flags to warn on potential null dereferences and to never delete any null pointer checks.
It sounds malicious, but it's simply oblivious. The same mechanism would delete null pointer checks in cases like these:
while (p && p->data != NUMBER) {
...
if (p && p->data < NUMBER) {
...
}
...
}
To enter the loop, the pointer can't be null, so that check can be rewritten to this:
if (p->data < NUMBER) { ... }
And it works for other things, too.
while (a && b && c)
if (a) {
...
} else if (b) {
...
} else if (c) {
...
}
Because the only way to enter the loop is for a and b and c to be true, that can be rewritten to the equivalent of this to remove unnecessary double checks:
for (;;) {
if (a) {
...
} else if (b) {
...
} else if (c)
...
} else break;
}
Edit:
Except, it doesn't rewrite source code. By the time it makes these optimizations, it's working with control flow graph in SSA form. These are all optimizations made after tons of passes, and it's harder to guess what the programmer intended. Some undefined behavior may be introduced by one pass and then gone after the next.
That's why there are both warnings about null pointer dereferences, and then it goes on to apply aggressive optimizations based on those null pointer dereferences anyway. The left hand doesn't know what the right hand is doing.