<table border="1" cellspacing="0" cellpadding="8">
    <tr>
        <th>Issue</th>
        <td>
            <a href=https://github.com/llvm/llvm-project/issues/56513>56513</a>
        </td>
    </tr>

    <tr>
        <th>Summary</th>
        <td>
            clang allocates the coroutine promise on the stack although it should put the promise on the heap
        </td>
    </tr>

    <tr>
      <th>Labels</th>
      <td>
            new issue
      </td>
    </tr>

    <tr>
      <th>Assignees</th>
      <td>
      </td>
    </tr>

    <tr>
      <th>Reporter</th>
      <td>
          pubg-hacks
      </td>
    </tr>
</table>

<pre>
    So I opened this issue https://github.com/llvm/llvm-project/issues/56455 but didn't receive any response and I thought this is to due lack of reproducible code that demonstrates the bug and the somewhat strange title so I opened this new issue after had a code to reproduce the problem:

The problem occurs with clang 14.0.6 with max optimizations due to allocating a promise of a coroutine on the stack while the coroutine return object `task` has escaped but clang didn't detect that !
The exception allocated on the stack turned to be not a problem but it the Microsoft c++ abi which clang adapts on windows.
The problem is tricky because it only happens when escaping the pointer with a pure virtual noexcept method using the abstract base


code to demonstrate the problem:
on godbolt: https://godbolt.org/z/qTdaMEj5o

code here on github:

```
#include <atomic>
#include <thread>
#include <condition_variable>
#include <coroutine>
#include <variant>
#include <deque>
#include <cassert>

// executor and operation base

class bug_any_executor;

struct bug_async_op_base
{
        void invoke()
        {
                invoke_operation();
        }

protected:

        ~bug_async_op_base() = default;

        virtual void invoke_operation() = 0;
};

class bug_any_executor
{
        using op_type = bug_async_op_base;

public:

        virtual ~bug_any_executor() = default;

        // removing noexcept enables clang to find that the pointer has escaped
        virtual void post(op_type& op) noexcept = 0;

        virtual void wait() noexcept = 0;
};

class bug_thread_executor : public bug_any_executor
{
        void work_thd()
        {
                while (!ops_.empty())
                {
                        std::unique_lock<std::mutex> lock{ lock_ };
                        cv_.wait(lock, [this] { return !ops_.empty(); });

                        while (!ops_.empty())
                        {
                                bug_async_op_base* op = ops_.front();
                                ops_.pop_front();
                                op->invoke();
                        }
                }

                cv_.notify_all();
        }

        std::mutex lock_;
        std::condition_variable cv_;
        std::deque<bug_async_op_base*> ops_;
        std::thread thd_;

public:

        void start()
        {
                thd_ = std::thread(&bug_thread_executor::work_thd, this);
        }

        ~bug_thread_executor()
        {
                if (thd_.joinable())
                        thd_.join();
        }

        // although this implementation is not realy noexcept due to allocation but I have a real one that is and required to be noexcept
        virtual void post(bug_async_op_base& op) noexcept override
        {
                {
                        std::unique_lock<std::mutex> lock{ lock_ };
                        ops_.push_back(&op);
                }
                cv_.notify_all();
        }

        virtual void wait() noexcept override
        {
                std::unique_lock<std::mutex> lock{ lock_ };
                cv_.wait(lock, [this] { return ops_.empty(); });
        }
};

// task and promise

struct bug_final_suspend_notification
{
        virtual std::coroutine_handle<> get_waiter() = 0;
};

class bug_task;

class bug_resume_waiter
{
public:
        bug_resume_waiter(std::coroutine_handle<> waiter) noexcept : waiter_{ waiter } {}

        constexpr bool await_ready() const noexcept { return false; }

        std::coroutine_handle<> await_suspend(std::coroutine_handle<>) noexcept
        {
                return waiter_;
        }

        constexpr void await_resume() const noexcept {}

private:
        std::coroutine_handle<> waiter_;
};

class bug_task_promise
{
        friend bug_task;
public:

        bug_task get_return_object() noexcept;

        constexpr std::suspend_always initial_suspend() noexcept { return {}; }

        bug_resume_waiter final_suspend() noexcept 
        {
                return bug_resume_waiter{ waiter_.index() == 0 ? std::get<0>(waiter_) : std::get<1>(waiter_)->get_waiter() };
        }

        void unhandled_exception() noexcept
        {
                ex_ptr = std::current_exception();
        }

        constexpr void return_void() const noexcept {}

        void get_result() const
        {
                if (ex_ptr)
                        std::rethrow_exception(ex_ptr);
        }

        std::variant<std::monostate, std::exception_ptr> result_or_error() const noexcept
        {
                if (ex_ptr)
                        return ex_ptr;
                return {};
        }

private:
        std::variant<std::coroutine_handle<>, bug_final_suspend_notification*> waiter_;
        std::exception_ptr ex_ptr = nullptr;
};

class bug_task
{
        friend bug_task_promise;
        using handle = std::coroutine_handle<>;
        using promise_t = bug_task_promise;

        bug_task(handle coro, promise_t* p) noexcept : this_coro{ coro }, this_promise{ p }
        {
                //printf("task(%p) coroutine(%p) promise(%p)\n", this, this_coro.address(), this_promise);
        }

public:

        using promise_type = bug_task_promise;

        bug_task(bug_task&& other) noexcept
                : this_coro{ std::exchange(other.this_coro, nullptr) }, this_promise{ std::exchange(other.this_promise, nullptr) }
        { 
                printf("task(task&&: %p) coroutine(%p) promise(%p)\n", this, this_coro.address(), this_promise); 
        }

        ~bug_task()
        {
                if (this_coro) {
                        //printf("~task(%p) coroutine(%p) promise(%p)\n", this, this_coro.address(), this_promise);
                        this_coro.destroy();
                }
        }

        constexpr bool await_ready() const noexcept
        {
                return false;
        }

        handle await_suspend(handle waiter) noexcept
        {
                assert(this_coro != nullptr && this_promise != nullptr);
                this_promise->waiter_ = waiter;
                return this_coro;
        }

        void await_resume() 
        {
                return this_promise->get_result();
        }

        bool is_valid() const noexcept
        {
                return this_promise != nullptr && this_coro != nullptr;
        }

        void start_coro(bug_final_suspend_notification& w) noexcept
        {
                assert(this_promise != nullptr && this_coro != nullptr);
                this_promise->waiter_ = &w;
                this_coro.resume(); // never throws since all exceptions are caught by the promise
        }

private:
        handle this_coro;
        promise_t* this_promise;
};

bug_task bug_task_promise::get_return_object() noexcept
{
        return { std::coroutine_handle<bug_task_promise>::from_promise(*this), this };
}

// spawn operation and spawner

template<class Handler>
class bug_spawn_op final : public bug_async_op_base, bug_final_suspend_notification
{
        Handler handler;
        bug_task task_;

public:

        bug_spawn_op(Handler handler, bug_task&& t)
                : handler { handler }, task_{ std::move(t) } {}

        virtual void invoke_operation() override
        {
                printf("starting the coroutine\n");
                task_.start_coro(*this);
                printf("started the coroutine\n");
        }

        virtual std::coroutine_handle<> get_waiter() override
        {
                handler();
                return std::noop_coroutine();
        }
};

struct dummy_spawn_handler_t
{
        constexpr void operator()() const noexcept {}
};

void bug_spawn(bug_any_executor& ex, bug_task&& t)
{
        using op_t = bug_spawn_op<dummy_spawn_handler_t>;
        op_t* op = new op_t{ dummy_spawn_handler_t{}, std::move(t) };
        ex.post(*op);
}

class bug_spawner;

struct bug_spawner_awaiter
{
        bug_spawner& s;
        std::coroutine_handle<> waiter;

        bug_spawner_awaiter(bug_spawner& s) : s{ s } {}

        bool await_ready() const noexcept;

        void await_suspend(std::coroutine_handle<> coro);

        void await_resume() {}
};

class bug_spawner
{
        friend bug_spawner_awaiter;

        struct final_handler_t
        {
                bug_spawner& s;

                void operator()()
                {
                        s.on_spawn_finished();
                }
        };

public:

        bug_spawner(bug_any_executor& ex) : ex_{ ex } {}

        void spawn(bug_task&& t)
        {
                using op_t = bug_spawn_op<final_handler_t>;
                // move task into ptr
                op_t* ptr = new op_t(final_handler_t{ *this }, std::move(t));
                ++count_;
                ex_.post(*ptr); // ptr escapes here thus task escapes but clang can't deduce that unless post() is not noexcept
        }

        bug_spawner_awaiter wait() noexcept { return { *this }; }

        void on_spawn_finished()
        {
                if (!--count_ && awaiter_)
                {
                        auto a = std::exchange(awaiter_, nullptr);
                        a->waiter.resume();
                }
        }

private:

        bug_any_executor& ex_; // if bug_thread_executor& is used instead enables clang to detect the escape of the promise
        bug_spawner_awaiter* awaiter_ = nullptr;
        std::atomic<std::size_t> count_ = 0;
};

bool bug_spawner_awaiter::await_ready() const noexcept
{
        return s.count_ == 0;
}

void bug_spawner_awaiter::await_suspend(std::coroutine_handle<> coro)
{
        waiter = coro;
        s.awaiter_ = this;
}

template<std::invocable<bug_spawner&> Fn>
bug_task scoped_spawn(bug_any_executor& ex, Fn fn)
{
        bug_spawner s{ ex };
        std::exception_ptr ex_ptr;

        try
        {
                fn(s);
        }
        catch (const std::exception& ex) // ex instead of ... to observe the address of ex
        {
                printf("caught an exception from fn(s): %p\n", std::addressof(ex));
                ex_ptr = std::current_exception();
        }

        co_await s.wait();
        if (ex_ptr)
                std::rethrow_exception(ex_ptr);
}

// forked task to start the coroutine from sync code

struct bug_forked_task_promise;

class bug_forked_task
{
        friend struct bug_forked_task_promise;
        bug_forked_task() = default;
public:
        using promise_type = bug_forked_task_promise;
};

struct bug_forked_task_promise
{
        bug_forked_task get_return_object() noexcept { return {}; }

        constexpr std::suspend_never initial_suspend() noexcept { return {}; }

        constexpr std::suspend_never final_suspend() noexcept { return {}; }

        void unhandled_exception() noexcept
        {
                std::terminate();
        }

        constexpr void return_void() const noexcept {}
};

// test case

bug_task bug_spawned_task(int id, int inc, std::atomic<int>& n)
{
        int result = n += inc;
        std::string msg = "count in coro (" + std::to_string(id) + ") = " + std::to_string(result);
        printf("%s\n", msg.c_str());
        co_return;
}

bug_forked_task run_coros(bug_any_executor& ex)
{
        std::atomic<int> count = 0;
        auto throwing_fn = [&](bug_spawner& s)
        {
                int stack_ptr = 0;
                printf("stack ptr: %p\n", std::addressof(stack_ptr));
                s.spawn(bug_spawned_task(1, 2, count)); // the coroutine frame is allocated on the stack !
                s.spawn(bug_spawned_task(2, 3, count));
                s.spawn(bug_spawned_task(3, 5, count));
                throw std::runtime_error{ "catch this !" }; // allocated on the stack as required by msvc c++ abi
        };

        try
        {
                co_await scoped_spawn(ex, throwing_fn);
        }
        catch (const std::exception& ex)
        {
                printf("scoped_spawn propagated exception: %s\n", ex.what());
        }

        printf("count after scoped_spawn: %d\n", count.load());
}


int main()
{
        int var = 0;
        bug_thread_executor ex;
        printf("stack address: %p\n", std::addressof(var));
        run_coros(ex);
        ex.start();
        ex.wait();
        return 0;
}
```



</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJzFG01z4jj215CLqqkEGpIcckjS3bVzmNPO3SVsETwxFmPJJJlfv-89ybIsycaZ7t2totOApff9LbGTxcfDvyX7jcmTqEXB9KFUrFSqFeyg9Ukt1o-L1Q94vZT60O6WuTzCh6o6d_99OTXyT5Fr-EjbFLzZbL9uNmzXalaURb1Y3WrWiFyUZ8F4_QHv1UnWCj8UgFkfZPty0B1qpiUrAH3F81cm97AaMBRtXu4qwXJZCFjIAbI4AgzdcC1gy0EAuhcCiO-VPIo3XIUL6hfYUuoKvw4YrcWbZZbvtWjYgReMWyTSYRYEE94DBUcUyPW3xbX9-0f_hMk8bxvF3kBSLK8AL7v5urxebs03R_4OuHV5LP_mugTaiUtAw6tK5vAVrOcI61iCaIBxJKSRLTyAj7XhS6NQ3g5lZWjqFzRCt03N5A51wRbba83VK_wHLCkmVM5PwDNqxBDm9FIIjRtIpIvVTc-UeM_FCens6IP9AyoQH8pRsp1gtdSGeBIE4ik1rf29zBup5B4QL1ZP8GJ8VyIHeScjXvCTVgj7rawL-aaWsWTRKpoyf_0AXDlvQT4AXtbVB7B3An2C0A-iNnyiHElfsqxRpyR8oK1tBDuXjW55BeQa9thRgPUVrFXdLr5DkwGJ7LgSvqLN3840POtLGgdw8yKLnaw0fBV6knmwlM0LfPob_v31R8F___7nRkaoDqIh5RvvC2wP1Gtf5uNqXdZ51cK2xfqZazCkfLH-nnqoD43gxcjDXNZFiZrPzrwpOfA1utBa38hz2l7rkaeF-KsdhcyVEo23065B8YFlirzVsiFvB29uyJ0ihYF1KYVRIYOYk3WbFusnfxGosEVl4yr1UeeZPGUeoFu3-P4sy4KV9Vm-isXqbrG6d0_8VfAyazJHmF299hbdfvNpANtBHxRFqF5c-T0mjeCBlL6BFe55iyb2FOzr7NwjOiSIAFz3W4GmIZgR-UWCMb4DxOmPkyCoMclDyKd2V6Fphtx2VHdc-2hnMG3NowHXPCNJzslFjUasbLwB792XlCe4HgQKL1KmBXmSCtLcnWV1sdoC10iVQxSINAnkjZfacjO2b0IVxnGdWBhGFyPOGZoy-GXzCmCKaSM2KYbW3MiTypbieNIfdo-_LdoJL6XJlNePbV2Cj2eQPl7Bqd3Xx1aLd_BtRg9un-j_jA35trDyc7a0IqPVq2e22Dxh8l5svjHcbDNfitD1EwEdeN8A-mfYTHEKr4R_PoJdkE4J5L6RtU4Ege5Fi06w-fLCLyC0QQiKl_WhJfjYf4kihXxd7j8ySO0Xw5OvUNKcUZe_wz2PUwcDbMmlNvw_p-SHtoFiSW40LgCeW2Rz4wraPRQtjZ42egRJegtw0a5twv3Mot6jnqmqvChOG99CYNNZZY9mihQu_4R4RVl5xEzdojmqtUGTV6YKt0X48QTFjKi1yaxYKEss4TkUXC5oBdUrZmAo-36DMIplPq2GysVW6wACs3UDSi8br2o0sCbDbcJAosArz6JpykKMSe-_G6OMB7fqAORhiEJrIQLDpUPn_LwfXkwkl-Twy7ieHZZnxWSP1Sj3WQPFdoZMyDZII0UcZHZeZapV0BQUGUm3NNaZSIdWml70svVsdgBMWPY-oyxehM6QV9F8unKiJmzsIbTB7VF0oIfkRaHsPt6xurtEebdwUGo82q8z1JJ5ixpBpcUWl2OXI95PDdtJWTGO6zMMW1adjBZ44HvN73lFlR-byCcjdBs0VouX-fQZHLN8S1TH-qSb9UyTo3VMo-xHuY5q-vIMvaGvwHnKymbbVTb0BI_dfVOC3GIDHMuP3UKydCOozAwSgvgSl1G9qBx7nfPx6o1_QC6poSDofTIqfb0KzkgxaTGR9bOBp0dQp60g9iXnCtkSegOIgc7TydnhzY-eQxAT6OyaLO-uUxutfgwX3YSLsISLA0oQX-PAj3bY1sZesGSws5lQPyNsi_fspJthaZO3TQMZPoT1Kc-wtoLv5zpGx4wxNYWNnLdzuvoxbMQFj-MJ6Dk08m3AU79pXonrhhZeVpQ1lCLozpDm3LcOCcEHBzbsZLLJIAm7lnUokX_GoDVb-3idMmnnPGM8TkWkBM9jkfb5Upo11XsizqYFxzzbrNuqGjB4KbNeinwuRHpkmHGFYSrwiDTL0V4LNNNu2DGCKwivoGCLFjGhKB0k7BnDUcIj1eIZrYXwhP-bysl0GQ4fPDuxQWkZFLtUQIH6a70nm1xZYharzclYaDfG67_rgLtvFptn0O2q73Gee_KWvCjA-FXXkAQETg-_RnJSIGp_tDRb2v3bLfUM-jAshgZSCuXtm-sBzxBw8oMQlv0y4LSzWRvFE8q5AMhJKYLl6ZP5pCZU2XOJfPxPFcvGNOs1up29XexunWDvWWLYEhkywP__2rLtt7u9hYBWRH4kMmnQ_P1snX2hvulK7wmENhKFlbb9Om4cxjDaQb2nPDpG6qM5s97nizFYEgvLX4wVk00nFAIsbcks2FvQ5XIqUdZfEGtIVVjDTCMlxQKAM69GyqVPoJ8SckIJM8RB8zHrfXeXcvyWvX3eOP4h6Z8yDoD0llpO7unrmiKXGS3U4gz9BFWOikHayQUOtfoDUMV4Azmb0yH17qM77_OnEDOKLetZSQsd1AGDeDNeCrmmLU6Jtv2YbuWi2qkvJCdLohjdd7N2D58zP9Y-dsNQG0SHbU5ghUYV6sTfau9MDwc-9J0bkNBfLY6niuT7bCrCfxF1jTsv7OtE2p3Jk-kXo_OS4VzxYnEbCc0itvXkwM-cgkhaM0fVPskgxBC8pdAranTYLeBxs92Fquzfm-KEaPFVfJRnVJe2VcdIyzbjPPHS7NFP3BRsuiP3Pmd3eTnyeKR6OYhQnnmtp9GIYhaWUZ4_PR68JAinzESVYJ3Q4awlGOewqJk7OLVT0aI9Hj-sTVnMWcL7g87eKNcdSlxu7iP0BMaZczfHHxzpbhkOWqZMOnnM7BoB5yjr5zSXw-4N93qHc3j3h74Cb0hvN_z5PX_gLD508b60BxaAI5j-D20rCE5i4k6CXZDx5JDYjxeC5KlGTuWmpsPJDipEbNQ3wNQNvCiajIeOefVsfGbel2iz58C2r06c9o4WfJMmHCtqauAQiiwiwurVpJfQFaMoMaHZftGorw47j-jwaylra-1ATakOopjVs3wyhzm7Sbm9sR7xTtlIvE_kHqpOvSAymv2GfE4HjFALw1Dhuk2GHm8OnyCtSIYFqbeoCylugtXFlNVdiACbeJOz2ERUSeiAbs3lsq11FjwC4Xkxx9XKXV1L0zW6z6LMPTJ9aJXhpfu6vxCY8-4-oL3wyDVr6wo6YeZQ3HcnwYm6P3L72CPSV18G8_-BiJIHAcbiR6x3xBLMdAHaii9fjBy7noP3c_lph-EtnnQPx4XeOKeH8zzetiCYvl0Jm5Ept5tsLDx5x26WeeYAUkjeOtiiVlslsLCDEoAX8X0pd0lUWMvB66mpNiiZOh6dnNNTXi9TdZcW-zm0Kv8W5J6sU92Fw1fKN8l4TAjmDVbitkgte_wxCaN1Txr_5zNaRFZ3cAukhN2kWg7kTWXyCLFeI-UowfI-N7c-n4dJCAn6UbsmyzU4KocUVMwo9H7UbF8n2fEQmYrCZITZ5wdxdtTNx1g8QBruJq7p3EO3lx8wZhjjiDF7Say7k-q8B1xjuVyi28idEs3ZXBC2Y0V8ChtntEh23sBr7yY2ttjMo96OevtxZu9IBp3c06lSMrH8wkNBY-PgI32E97dMHG599uguOTXYy-YVuzxqtqWZZAXX40ly2OzT5f6RatvAmTpf6CtCb-1oVTgb9H0Icfy2a3wxZOKkZBLrWMM4tjHps97Ci7cH5p71T9wpMKO6X3Ol4CKa6RsG85D85MF9fxFRNEcgR0-OAEZ6-c-d0o9ewBKwJQ_u2A-GkCZ-OwuGUMZKuhRJ7-p8GKC6TF-aHwhAQE1nBtxshuymemBYEMMbBJjKDmDF6A9H9WIHwitK3LDeHqFSdEUo3jVPmZltSHZhwjrWTavOCyd3dEcAA60MjqlWG-UFaSBtmeP2_gKntxGiqVHYWNAL3a5pa5oQqalmK5brmCZMnRVdZTclMMVo4Drb10YumyesCzbf0gOC0YK81uZHRC4DXQfJKZji5a_Uec1MeA50Mu-ppV-oBEZ7g0AJMonBAejyfJhV-FHQ3db0T6TcD6pmYCak6xjzbAC0dzMNgPTXCw1MR5dHYe-rUPu1MrWPacKAfDR8G9y6m8JJVrnqL_fuPsDGz7n_i6-JMcJ0rdYXF8Mi0xSUnj3-knJuTmHmE4Jp98RfSBw9NGOmvsuL9yX-HDHp8HH8HpSB5I3m14kDERgkhYeEli4ryYsEnhAJ_kU3PHJ3TzwZes884aCpX6LgveF0-LMGYs_VZ_ow4I1F5Yc6o6_hANa_4z98ki5MbQJP9XLBT-u8v1fFw7q4X9_zK_pR6YP9FaN1CxWECPd7zoGzdLft0a7hXVWwU6v9jrpbfxD8dNU21cPP_Bh3c7O-OjyIG7G646vbzfZ-s99st3zHV_v1an-7u7_-mu_XVxXfiUo9UExfuV_GopI2367Kh9X1anV9e7O-2d5s1-vlTX53vV_f3203X7f7Hcj167UAY6qWSAf-tvGqeSCSwFgUPKxKpVX_EOro8qUWgtABfEgvB9k8QHn78uUAMlJXhP2BqP8PndsRZg">