4 ms·
>If the pointer address cannot be inferred at compile time (e.g. via address of stack variables, or constant propagation), the compiler cannot optimize away the
by Kranar 4y ago
>If the pointer address cannot be inferred at compile time (e.g. via address of stack variables, or constant propagation), the compiler cannot optimize away the dereference.
It can't optimize away the dereference, but it can optimize away any NULL checks, so for example the compiler can transform this:
if(y != NULL) {
printf("Hello");
}
int x = *y;
Into this:
printf("Hello");
int x = *y;
It's possible that you never expect "Hello" to be printed if y is NULL, since there is a clear check for NULL before the printf, and yet... because of undefined behavior and compiler optimizations, the compiler is allowed to make assumptions about runtime behavior even if that behavior precedes an undefined operation.
https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633 https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=63...
- 10000truths 4y agoIn your example, even though the behavior is undefined according to the spec, the program will still reliably segfault as long as the target it runs on has the appropriate memory protections for the zero page. The crash will occur immediately after the elided null check, so the failure is localized to the problematic area and a debugger will show the problem.
- int_19h 4y agoIt won't occur immediately after the elided check, that's exactly the problem. It will allow the I/O to occur first. Now imagine if that was a database update.
- repsilat 4y agoMaybe off-topic, but here's an example of a null dereference that usually won't segfault because it never actually loads the address: struct X { int foo() { return 1; } }; X *x = nullptr; cout << x->foo() << endl;
- Kranar 4y agoFirst your statement is unfortunately just untrue, the idea that any behavior can be relied upon when performing an undefined operation is why to this day we have very buggy software written in C and C++ that lead to security vulnerabilities. There is nothing reliable about undefined behavior. Second, even if we accept for the sake of argument that your statement is true, the entire point of my example is that by the time you get to the dereference which produces a segfault, it's already too late. Any operation prior to that segfault that assumed a non-null pointer (such as the print statement) will have already been executed and produced potential side-effects. There is nothing local about this, it's simply that for the sake of an example I wrote a small code snippet, but in the article I linked to there are examples of very non-local bugs both spatially and temporally that would not be something you could reliably track down in a debugger.
- comex 4y agoCompilers would be allowed to do this… but they don't do it. See for yourself: https://gcc.godbolt.org/z/4b5azx5ac https://gcc.godbolt.org/z/4b5azx5ac I replaced the printf with a simple write to a global variable, since as-is, the optimization might actually be illegal on the grounds that the printf might never return (e.g. if stdout is a blocked pipe). But none of GCC, Clang, or MSVC elide the check at max optimization level. If you put the dereference before the null check, then all of those compilers do elide it. This is pretty easy to justify, since any program without address 0 mapped will always crash before reaching the check. This optimization did cause a brouhaha a decade ago, when it was discovered that it was being applied to the Linux kernel. At the time, it was possible for a user program to map address 0 and have that be directly dereferenceable by the kernel, and an optimized-away null pointer check made an otherwise unexploitable kernel bug exploitable. [1] Linux now passes a flag to turn the optimization off (-fno-delete-null-pointer-checks), which solves that problem without denying user programs the benefits of the optimization. That said, it appears that Clang elides the check in the dereference-before-check version even on WebAssembly; this is probably a bug. [1] https://lwn.net/Articles/342330/ https://lwn.net/Articles/342330/
- codeflo 4y agoIt's dangerous to assume that you know better than someone like Raymond Chen, the author of the article the GP linked. This is not a Clang bug, it's just the nature of UB optimizations we're talking about. :) I've found a simple example. Code order and compiler options are very finicky with this sort of thing, but as of posting this, clang optimizes away the null dereference here entirely: https://gcc.godbolt.org/z/zjhY1W67Y https://gcc.godbolt.org/z/zjhY1W67Y (Edit: Simplified example even a bit more.)