public inbox for gcc-bugs@sourceware.org
help / color / mirror / Atom feed
* [Bug c++/98401] New: Temporaries passed to co_await sometimes cause an extraneous call to destructor at incorrect address
@ 2020-12-20 16:39 brandt.milo2 at gmail dot com
  2020-12-20 16:40 ` [Bug c++/98401] " brandt.milo2 at gmail dot com
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: brandt.milo2 at gmail dot com @ 2020-12-20 16:39 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98401

            Bug ID: 98401
           Summary: Temporaries passed to co_await sometimes cause an
                    extraneous call to destructor at incorrect address
           Product: gcc
           Version: 10.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: brandt.milo2 at gmail dot com
  Target Milestone: ---

Created attachment 49811
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=49811&action=edit
main file

I was writing some code using coroutines the other day and started noticing
that the destructor of a std::string was crashing in certain situations. The
problem seems to arise in gcc's handling of passing a temporary to co_await,
but it's a bit finicky when exactly this arises. I found a minimal working
example, which is attached (both the original .cpp and the post-pre-processed
.ii file, although only standard library headers are #include'd). It does the
following:

(1) Defines a class lifetime_tester that prints something whenever its
constructed, assigned, or destructed. It also has a single member that must, by
all rights, always equal this to demonstrate some further perplexing behavior.

(2) Defines a struct tag whose single element is a lifetime_tester.

(3) Defines the simplest possible coroutine, which just runs the code without
suspending and has a single await_transform member accepting a tag&& and
outputting the address and value of the member of that reference.

(4) A coroutine which passes a temporary of type tag.

(5) An invocation of that coroutine in main().

The expected output of this program (and the output I get when compiled with
clang++ and the experimental parts of libc++) would be something like:

Constructed at 0x1ba32e0
Poked at 0x1ba32e0 with ptr to 0x1ba32e0
Destructed at 0x1ba32e0
End of coroutine body.

Where a lifetime_tester is constructed at some address, observed at that
address, then destructed. However, when I compile the code with

g++-10 -std=c++20 -fcoroutines main.cpp -o program

where g++-10 --version gives g++-10 (Ubuntu 10.2.0-5ubuntu1~20.04) 10.2.0.

I get some very surprising output:

Constructed at 0x560814d21ee0
Poked at 0x560814d21ed8 with ptr to 0x560814d21ee0
Destructed at 0x560814d21ed8
Destructed at 0x560814d21ee0
End of coroutine body.

The constructor is called at some address, but then when we look at the
temporary passed to co_await, its address is 8 bytes higher than where the
constructor was called (yet it somehow has a member pointing to the original
address). Then, the destructor is called at this higher address and then called
again at the correct address. The difference between the two addresses is
consistent between runs.

Various perturbations of the code yield varying results:

* If we change the signature of await_transform to auto await_transform(tag),
the bug remains.

* If we construct a local variable of type tag in the coroutine, then pass it
to co_await (possibly calling std::move on it), the program behaves correctly,
regardless of whether we pass by rvalue or lvalue reference or by value.

* If we remove the struct tag and just pass lifetime_tester, the program
behaves as expected.

* If we change the struct to be struct tag { int x; lifetime_tester tester; };
the incorrect address appears 8 bytes lower than the original one instead of 8
bytes higher. Similarly if we did struct tag{ lifetime_tester tester; int x; };

* If we leave the struct tag as it originally was, but add more elements to
lifetime_tester (e.g. add a member char padding[1024]), the difference between
the addresses increases - and seems to always equal -sizeof(lifetime_tester)
bytes.

I also tried compiling the master branch of the gcc git repository and noticed
the same behavior when compiling the program (--version gives g++ (GCC) 11.0.0
20201219 (experimental)). The same behavior happens if I try the code on
godbolt on various recent gcc branches (https://godbolt.org/z/fx6YPb) - though,
oddly, selecting x86-64 gcc 10.2 on the compiler explorer produces a different
incorrect output:

Constructed at 0x21cbed8
Poked at 0x21cbed8 with ptr to 0x21cbed8
End of coroutine body.

Where the destructor is not called on the temporary at all.

Attached is the original main.cpp file which can be compiled with the command
listed earlier.

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2021-12-28 10:03 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-12-20 16:39 [Bug c++/98401] New: Temporaries passed to co_await sometimes cause an extraneous call to destructor at incorrect address brandt.milo2 at gmail dot com
2020-12-20 16:40 ` [Bug c++/98401] " brandt.milo2 at gmail dot com
2021-01-09 19:56 ` dpsicilia at gmail dot com
2021-06-10 14:28 ` davidledger at live dot com.au
2021-06-23  8:55 ` victor.burckel at gmail dot com
2021-11-06  5:07 ` [Bug c++/98401] coroutines: " pigman46 at gmail dot com
2021-12-28  4:02 ` brandt.milo2 at gmail dot com
2021-12-28  4:03 ` brandt.milo2 at gmail dot com
2021-12-28  4:04 ` brandt.milo2 at gmail dot com
2021-12-28 10:03 ` iains at gcc dot gnu.org

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).