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.

Reply via email to