From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 48) id D1FD83850414; Sun, 20 Dec 2020 16:39:02 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org D1FD83850414 From: "brandt.milo2 at gmail dot com" To: gcc-bugs@gcc.gnu.org Subject: [Bug c++/98401] New: Temporaries passed to co_await sometimes cause an extraneous call to destructor at incorrect address Date: Sun, 20 Dec 2020 16:39:02 +0000 X-Bugzilla-Reason: CC X-Bugzilla-Type: new X-Bugzilla-Watch-Reason: None X-Bugzilla-Product: gcc X-Bugzilla-Component: c++ X-Bugzilla-Version: 10.2.0 X-Bugzilla-Keywords: X-Bugzilla-Severity: normal X-Bugzilla-Who: brandt.milo2 at gmail dot com X-Bugzilla-Status: UNCONFIRMED X-Bugzilla-Resolution: X-Bugzilla-Priority: P3 X-Bugzilla-Assigned-To: unassigned at gcc dot gnu.org X-Bugzilla-Target-Milestone: --- X-Bugzilla-Flags: X-Bugzilla-Changed-Fields: bug_id short_desc product version bug_status bug_severity priority component assigned_to reporter target_milestone attachments.created Message-ID: Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable X-Bugzilla-URL: http://gcc.gnu.org/bugzilla/ Auto-Submitted: auto-generated MIME-Version: 1.0 X-BeenThere: gcc-bugs@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-bugs mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 20 Dec 2020 16:39:02 -0000 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=3D98401 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=3D49811&action=3Dedit 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-process= ed .ii file, although only standard library headers are #include'd). It does t= he 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 behavi= or. (2) Defines a struct tag whose single element is a lifetime_tester. (3) Defines the simplest possible coroutine, which just runs the code witho= ut 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=3Dc++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 ca= lled 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 correct= ly, 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 o= f 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 betw= een 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 noti= ced 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) - tho= ugh, oddly, selecting x86-64 gcc 10.2 on the compiler explorer produces a differ= ent 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 comma= nd listed earlier.=