public inbox for gcc-bugs@sourceware.org
help / color / mirror / Atom feed
* [Bug c++/104872] New: Memory corruption in Coroutine with POD type
@ 2022-03-10 19:59 benni.buch at gmail dot com
  2022-03-11  9:05 ` [Bug c++/104872] " benni.buch at gmail dot com
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: benni.buch at gmail dot com @ 2022-03-10 19:59 UTC (permalink / raw)
  To: gcc-bugs

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.

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

end of thread, other threads:[~2022-11-02 11:48 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-03-10 19:59 [Bug c++/104872] New: Memory corruption in Coroutine with POD type benni.buch at gmail dot com
2022-03-11  9:05 ` [Bug c++/104872] " benni.buch at gmail dot com
2022-03-11  9:18 ` benni.buch at gmail dot com
2022-06-21 12:48 ` benni.buch at gmail dot com
2022-11-02 11:15 ` benni.buch at gmail dot com
2022-11-02 11:25 ` redi at gcc dot gnu.org
2022-11-02 11:48 ` redi 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).