Description
Currently, when optimizations are enabled, flowing off the end of a function in C++ mode emits unreachable
. This means that a function such as:
int get(int x) {
if (x == 0) { }
else std::abort();
}
... is equivalent to simply calling abort()
, which is caused by emitting unreachable
at the end of the function, and having SimplifyCFG
eliminate the x == 0
branch. This behavior does not reflect developer intent, and it's possible to fall through into other code sections as a result of simply treating this as optimizable UB.
On debug builds, @llvm.trap()
is emitted at the end of the function, which generates a ud2
pseudo-instruction on x86-64. This should also be done with optimizations enabled.
Even with no warning flags enabled, clang emits
<source>:6:1: warning: non-void function does not return a value in all control paths [-Wreturn-type] 6 | } | ^
Flowing off the end of a function is a common developer mistake and basically never intentional. It should not be hidden by the optimizer because it may result in security vulnerabilities such as CVE-2014-9296.
If a developer actually wants the current behavior, they can simply write:
int get(int x) {
if (x == 0) {
__builtin_unreachable(); // or, since C++23
std::unreachable(); // or, alternatively
[[assume(false)]];
}
else std::abort();
}
There is zero motivation for violating programmer intent and emitting security vulnerabilities here. This offers no optimization opportunities that could not be trivially obtained with more explicit code.