The highly condensed version is that the compiler optimized away an if block that was responsible for throwing an error as impossible to reach in correctly functioning code, when the whole purpose of that if block was to check that condition and error so that the program did not continue in an invalid state.
Specifically:
word* target = segmentStart + farPointer.offset;
if (target < segmentStart || target >= segmentEnd) {
throwBoundsError();
}
doSomething(*target);
(a simplified version of the actual code) was used to detect if target had overflowed (and thus target < segmentStart). The bug report goes on to explain:However, as it turns out, pointer arithmetic that overflows is undefined behavior under the C standard. As a result, the compiler is allowed to assume that the addition on the first line never overflows. Since farPointer.offset is an unsigned number, the compiler is able to conclude that target < segmentStart always evaluates false. Thus, the compiler removes this part of the check. Unfortunately, in the case of overflow, this is exactly the part of the check that we need.
The post that's from (the sumbmitted article to the HN discussion I linked) is fairly accessible in my view. I highly recommend reading it.
Another discussion that probably has good info is this one.[2]
There are undoubtedly ways to tell the compiler to be careful, but that isn't always in control of the person writing the code, and even if some level of control exists, compilers change.
In practice there is very little difference and you're effectively entering the realm of UB as soon as the program starts, because through inlining and propagation a possible UB may be leveraged before the UB is sequentially hit.
When people say "just fix this in compilers, don't compile when they run into UB" it's a fundamental misunderstanding of the problem. Yes, in some cases the compiler can prove UB, and uses it to write optimiations, but it doesn't prove that all code is not invoking UB.
No kidding! There is one right here:
> Yes, in some cases the compiler can prove UB, and uses it to write optimiation
Compilers do not prove UB , on the contrary they postulate no-UB and then use this axiom to prove other properties of the code.
I think it's still a (cause of) misunderstanding. For the compiler, UBs are situations which axiomatically can not occur. "This is UB" is not a concept, because the compiler assumes at all point that UBs can not occur.
The compiler doesn't go "oh you're dereferencing a pointer which may be null, fuck you", it goes "you're dereferencing a pointer so it can't be null, and thus I can remove anything assuming possible nullability".
:) This is what I'm trying to say.
How is a compiler supposed to reject this for undefined behavior? It'd be absurd to demand the compiler someone knows all possible usages for get_value to find if any of them pass a nullptr, so what is it supposed to do?
And this is why the spec says things like that deref'ing a null pointer is undefined behavior, so that the compiler can take that function and do the thing you'd expect it to do and transform it into a simple memory read.
CppCon has also had a lot of great talks on UB and why it is what it is, such as https://www.youtube.com/watch?v=yG1OZ69H_-o
The article you linked discusses what happens when you do things you should not, like fail to initialize a variable before using it. Modern compilers, when used correctly will let you know when you do this. With the right compiler options, it won't allow you to make such mistakes.
So no threads, no shared memory, no locking mechanisms, no interrupts. Just a compiler making valid optimization and removing a necessary guard condition.
Try again.
Deref'ing an empty (e.g. moved-from) unique_ptr is still UB.
(That'll only work for null.)
No. Even with smart pointers it's possible (move out of unique_ptr, deref). But even with no pointers it's possible - index into an array without checking the bound, signed int overflow, etc.
Articles like http://blog.llvm.org/2011/05/what-every-c-programmer-should-... have convinced me that even if C or C++ reads logically like it is safe, there is a possibility that the compiler can rewrite your code in an acceptable way according to the standards such that the checks that are clearly visible in your code disappear, opening up the very problems that you thought you were protected against.
Is there any possibility that after an aggressive compiler gets done with inlining and optimization that that could happen here in some way? Can it be proven that if the compiler works according to the standard that this won't happen..even if the programmer accidentally trips on undefined behavior?