【问题标题】:C++20 coroutine use after free issue免费发行后的C++20协程使用
【发布时间】:2021-09-17 11:43:25
【问题描述】:

我正在尝试学习和实现 C++20 协程,但遇到了一个错误。

生成器类:

template<class ReturnType = void>
    class enumerable
    {
    public:
        class promise_type;
        using handle_type = std::coroutine_handle<promise_type>;
        class promise_type
        {
        public:
            ReturnType current_value{};

            auto get_return_object()
            {
                return enumerable{ handle_type::from_promise(*this) };
            }

            auto initial_suspend()
            {
                return std::suspend_always{};
            }

            auto final_suspend() noexcept
            {
                return std::suspend_always();
            }

            void unhandled_exception()
            {
                // TODO:
            }

            void return_void()
            {

            }

            auto yield_value(ReturnType& value) noexcept
            {
                current_value = std::move(value);
                return std::suspend_always{};
            }

            auto yield_value(ReturnType&& value) noexcept
            {
                return yield_value(value);
            }
        };

        class iterator
        {
            using iterator_category = std::forward_iterator_tag;
            using difference_type = std::ptrdiff_t;
            using value_type = ReturnType;
            using pointer = ReturnType*;
            using reference = ReturnType&;
        private:
            handle_type handle;

        public:
            iterator(handle_type handle)
                : handle(handle)
            {

            }

            reference operator*() const
            {
                return this->handle.promise().current_value;
            }

            pointer operator->()
            {
                return &this->handle.promise().current_value;
            }

            iterator& operator++()
            {
                this->handle.resume();
                return *this;
            }

            friend bool operator==(const iterator& it, std::default_sentinel_t s) noexcept
            {
                return !it.handle || it.handle.done();
            }

            friend bool operator!=(const iterator& it, std::default_sentinel_t s) noexcept
            {
                return !(it == s);
            }

            friend bool operator==(std::default_sentinel_t s, const iterator& it) noexcept
            {
                return (it == s);
            }

            friend bool operator!=(std::default_sentinel_t s, const iterator& it) noexcept
            {
                return it != s;
            }
        };

        handle_type handle;

        enumerable() = delete;

        enumerable(handle_type h)
            : handle(h)
        {
            std::cout << "enumerable constructed: " << this << " : " << this->handle.address() << '\n';
        };

        iterator begin()
        {
            this->handle.resume();
            return iterator(this->handle);
        }

        std::default_sentinel_t end()
        {
            return {};
        }

        //Filters a sequence of values based on a predicate.
        template<class Predicate>
        enumerable<ReturnType> where(Predicate&& pred)
        {
            std::cout << "where: " << this << " : " << this->handle.address()  << '\n';
            for (auto& i : *this)
            {
                if(pred(i))
                    co_yield i;
            }
        }

        ~enumerable()
        {
            std::cout << "enumerable destructed: " << this << " : " << this->handle.address() << '\n';
        }
    };

测试代码:

enumerable<int> numbers()
{
    co_yield 1;
    co_yield 2;
    co_yield 3;
    co_yield 4;
}

enumerable<int> filtered_numbers()
{
    return numbers().where([](int i) { return true; });
}
// Crashes
int main()
{
    for (auto& i : filtered_numbers())
    {
        std::cout << "value: " << i << '\n';
    }
    return 0;
}

输出:

enumerable constructed: 000000FF0550F560:000002959E3B5290
enumerable constructed: 000000FF0550F5D8:000002959E3B6470
destructed: 000000FF0550F560 : 000002959E3B5290
where: 000000FF0550F560 : 000000FF0550F640
//Works, despite "this" inside "where" still being destructed before use, can be observed with the couts.
int main()
{
    for(auto i : numbers().where([](int i) { return true; }))
    {
        std::cout << "value: " << i << '\n';
    }
    return 0;
}

输出:

enumerable constructed: 000000C9EDD2FD78:000001DADD1A61D0
enumerable constructed: 000000C9EDD2FD28:000001DADD1A73B0
destructed: 000000C9EDD2FD78 : 000001DADD1A61D0
where: 000000C9EDD2FD78 : 000001DADD1A61D0
value: 1
value: 2
value: 3
value: 4
destructed: 000000C9EDD2FD28 : 000001DADD1A73B0

有人能解释一下这里发生了什么吗?如果可能的话,我想提出一个解决方法,如果我们在 promise_type 的“initial_suspend”中返回“std::suspend_never”,则不会发生崩溃,但是在 initial_suspend 中挂起对于生成器来说并不是理想的行为。

【问题讨论】:

    标签: c++ visual-c++ c++20


    【解决方案1】:

    这个:

    return numbers().where([](int i) { return true; });
    

    创建一个临时文件 (numbers()),然后将对该临时文件的引用存储在协程中(循环中使用的 *this),然后临时文件消失。

    这很糟糕。如果你想做协程的链接,则该链中的每个步骤都需要是某人堆栈上的一个对象。 where 可以是一个非成员函数,接受 enumerable 的值。这将允许where 协程保持enumerable 的存在。

    【讨论】:

    • 感谢您的解释,您是否碰巧知道保留相同语法的解决方案?我目前只是在尝试不引入更多开销的方法。我也很好奇为什么 main 的工作版本不会崩溃, (numbers()) 的临时值在 where 之前被破坏,但 coroutine_handle 仍然有效。
    • @ahhhaghost:未定义的行为是未定义的。谁知道为什么 UB 有时会起作用,而其他时候则不起作用。你将不得不改变语法。
    • where 也可以是一个 && 成员函数,它从这里移动并将其资源的所有权转移到返回的对象。
    【解决方案2】:
        //Filters a sequence of values based on a predicate.
        template<class Predicate>
        enumerable<ReturnType> where(Predicate pred)&
        {
            std::cout << "where: " << this << " : " << this->handle.address()  << '\n';
            for (auto& i : *this)
            {
                if(pred(i))
                    co_yield i;
            }
        }
        //Captures *this as well as above.
        template<class Predicate>
        enumerable<ReturnType> where(Predicate pred)&&
        {
            auto self=std::move(*this);
            std::cout << "where: " << this << " : " << this->handle.address()  << '\n';
            for (auto& i : self)
            {
                if(pred(i))
                    co_yield i;
            }
        }
    

    两个变化。

    1. 我采用Predicate 的值,以避免悬空引用问题。

    2. 我有一个 &amp;&amp; 重载,它复制 *this(好吧,从移动)并将其存储在协程中。

    这仍然不起作用。

    首先发生的事情是我们的协程在任何代码运行之前暂停。因此,auto self=std::move(*this) 的副本发生在我们第一次尝试获取值时。

    我们可以通过几种方式解决这个问题。其中之一是反弹到一个免费函数并让复制enumerable&lt;int&gt;

    template<class Predicate>
    friend enumerable<ReturnType> where( enumerable<ReturnType> self, Predicate pred ) {
      for (auto& i: self)
        if (pred(i))
          co_yield i;
    }
    //Filters a sequence of values based on a predicate.
    template<class Predicate>
    enumerable<ReturnType> where(Predicate pred)&
    {
       return where( *this, std::move(pred) );
    }
    template<class Predicate>
    enumerable<ReturnType> where(Predicate pred)&&
    {
       return where( std::move(*this), std::move(pred) );
    }
    

    第二种方法是修改enumerable&lt;ReturnType&gt; 以支持设置阶段。

    struct init_done {};
    
    auto initial_suspend() {
      return std::suspend_never{};
    }
    auto yield_value(init_done) noexcept {
      return std::suspend_always{};
    }
    

    并在设置完成后将enumerable&lt;int&gt; 返回函数修改为第一个co_yield init_done{};

    我们会在 numbers() 协程的第一行执行此操作,然后将 *this 复制到 where() 协程中的局部变量 self 中。

    这可能是最简单的:

    template<class F>
    friend
    enumerable<ReturnType> where2(enumerable<ReturnType> self, F f )
    {
        for (auto i : self.where(std::move(f)))
            co_yield i;
    }
    template<class F>
    enumerable<ReturnType> where(F f)&&
    {
        return where2(std::move(*this), std::move(f));
    }
    template<class F>
    enumerable<ReturnType> where(F f)&
    {
        for (auto i : *this)
        {
            if (f(i))
                co_yield i;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-06-26
      • 1970-01-01
      • 2017-09-16
      • 2020-01-22
      • 2019-10-04
      • 2021-08-27
      • 2020-10-15
      • 2020-02-26
      • 1970-01-01
      相关资源
      最近更新 更多