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.