https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104872
Bug ID: 104872 Summary: Memory corruption in Coroutine with POD type Product: gcc Version: 11.2.1 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: benni.buch at gmail dot com Target Milestone: --- The bug affects all versions of GCC 11 and trunk (upcoming version 12). The following minimal example causes a crash during memory freeing. However, the error seems to be caused by a previous incorrect memory access. At least that is what the log output of contructors, the assignment and the destructor suggests. Tested with Compiler Explorer: https://godbolt.org/z/WKf57cPzn ```cpp #include <coroutine> #include <iostream> #include <string_view> using namespace std::literals; class logging_string{ public: logging_string(std::string_view text) :text_(text) { std::cout << " view: " << this << " " << text_ << std::endl; } logging_string(logging_string&& other) { std::cout << " move: " << this << " <= " << &other << " new <= " << other.text_ << std::endl; text_ = std::move(other.text_); } ~logging_string(){ std::cout << " destruct: " << this << " " << text_ << std::endl; } logging_string& operator=(logging_string&& other){ std::cout << "move-assign: " << this << " <= " << &other << " " << text_ << " <= " << other.text_ << std::endl; text_ = std::move(other.text_); return *this; } private: std::string text_; }; struct wrapper{ // wrapper() = default; // wrapper(std::string_view text) :filename(text) {} // wrapper(wrapper&&) = default; // wrapper& operator=(wrapper&&) = default; // ~wrapper() = default; logging_string filename; }; struct generator{ struct promise_type; using handle_type = std::coroutine_handle<promise_type>; struct promise_type{ wrapper value{"default"sv}; std::exception_ptr exception; generator get_return_object(){ return handle_type::from_promise(*this); } std::suspend_always initial_suspend(){ return {}; } std::suspend_always final_suspend()noexcept{ return {}; } void unhandled_exception(){ exception = std::current_exception(); } std::suspend_always yield_value(wrapper&& new_value){ value = std::move(new_value); return {}; } void return_void(){} }; generator(handle_type h) : handle_(h) {} generator(generator&& other) : handle_(other.handle_) { other.handle_ = nullptr; } generator& operator=(generator&& other){ handle_ = other.handle_; other.handle_ = nullptr; return *this; } ~generator(){ if(handle_){ handle_.destroy(); } } explicit operator bool(){ fill(); return !handle_.done(); } wrapper operator()(){ fill(); full_ = false; return std::move(handle_.promise().value); } private: handle_type handle_; bool full_ = false; void fill(){ if(!full_){ handle_(); if(handle_.promise().exception){ std::rethrow_exception(handle_.promise().exception); } full_ = true; } } }; static generator generate(){ co_yield {"generate"sv}; } int main(){ auto gen = generate(); (void)static_cast<bool>(gen); } ``` Crashes with output: ```text view: 0xea2ec0 default view: 0xea2f20 generate move-assign: 0xea2ec0 <= 0xea2f00 default <= generate destruct: 0xea2f00 destruct: 0xea2f20 generate destruct: 0xea2ec0 generate free(): invalid size Aborted (core dumped) ``` The move-assign looks very strange because the address of the "other" object is not one of the previously created objects! If you uncomment the wrapper functions in the minimal example, the program runs fine and generates the expected output: ```text view: 0x129aec0 default view: 0x129af00 generate move-assign: 0x129aec0 <= 0x129af00 default <= generate destruct: 0x129af00 destruct: 0x129aec0 generate ``` Also the move-assign looks as expected now. You get the same valid output (regardless of whether the wrapper functions are defined or not) when compiling with trunk of clang and msvc. (Tested via Compiler Explorer.) I also tested clang trunk with "-stdlib=libstdc++" which also works fine. So the bug is probably in the g++ compiler, not in the libstdc++ library.