https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349
Richard Biener <rguenth at gcc dot gnu.org> changed:
What |Removed |Added
----------------------------------------------------------------------------
CC| |jason at gcc dot gnu.org
See Also| |https://gcc.gnu.org/bugzill
| |a/show_bug.cgi?id=101641
--- Comment #46 from Richard Biener <rguenth at gcc dot gnu.org> ---
(In reply to Christopher Nerz from comment #45)
> This is a critical bug which renders gcc unusable for safety relevant
> systems using expected/variant or simple ipc.
>
> You can get the same buggy behavior with far simpler code:
> https://godbolt.org/z/1WTnnYceM
>
>
> #include <cstdint>
> #include <memory>
>
> bool check()
> {
> // Just to prove that it is not a problem with alignment etc.
> static_assert(alignof(double) == alignof(std::uint64_t));
> static_assert(sizeof(double) == sizeof(std::uint64_t));
>
> alignas(8) std::byte buffer[8]; // some buffer
> new (buffer) double{1}; // some completely trivial data
> // reuse memory -> double ends lifetime, uint64 starts lifetime
> std::uint64_t * res = new (buffer) std::uint64_t;
> // *res is allowed to be used as it is the correct pointer returned by
> new
> // *res == 0x3ff0000000000000 // and gives correct value
> // The very definition of std::launder says that it is suppose to be
> used as:
> return (*res == *std::launder(reinterpret_cast<std::uint64_t*>(buffer)));
> }
>
> int main(int argc, char **argv) {
> return check(); // gives false with activatred O2 (true with O0)
> }
>
>
> We get the same behavior when initialisating the memory at our version of
> "std::uint64_t * res = new (buffer) std::uint64_t;", but were unable to give
> a minimal example for that behavior.
For this case we end up with an indetermined value for 'buffer' read as
uint64_t but that indetermined value is different from the one read after
.LAUNDER. A somewhat early IL is
MEM[(double *)&buffer] = 1.0e+0;
_1 = MEM[(uint64_t *)&buffer];
_12 = .LAUNDER (&buffer);
_3 = *_12;
_13 = _1 == _3;
we then re-interpret 1.0e+0 as uint64_t and then remove the store as dead
because there's no valid use - the *_12 load is done as uint64_t.
The effect is that the later load reads from uninitialized stack.
Note that .LAUNDER only constitutes a data dependence between the &buffer
and _12 pointer _values_ but there's no dependence of the memory contents
pointed to - .LAUNDER is ECF_NOVOPS. That makes the compiler forget
what _12 points to but it doesn't make later uint64 loads valid from
*_12 from an earlier store to double.