llvm/clang/test/CodeGenCoroutines/pr59723.cpp

// This is reduced test case from https://github.com/llvm/llvm-project/issues/59723.
// This is not a minimal reproducer intentionally to check the compiler's ability.
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fcxx-exceptions\
// RUN:     -fexceptions -O2 -emit-llvm %s -o - | FileCheck %s

#include "Inputs/coroutine.h"

// executor and operation base

class bug_any_executor;

struct bug_async_op_base
{
	void invoke();

protected:

	~bug_async_op_base() = default;
};

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
{

public:

	void start()
	{
		
	}

	~bug_thread_executor()
	{
	}

	// 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;

	virtual void wait() noexcept override
	{
		
	}
};

// task and promise

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

class bug_task;

class bug_task_promise
{
	friend bug_task;
public:

	bug_task get_return_object() noexcept;

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

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

	void unhandled_exception() noexcept;

	constexpr void return_void() const noexcept {}

	void get_result() const
	{
		
	}
};

template <class T, class U>
T exchange(T &&t, U &&u) {
    T ret = t;
    t = u;
    return ret;
}

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 }
	{
	
	}

public:
	using promise_type = bug_task_promise;

    bug_task(bug_task&& other) noexcept
		: this_coro{ exchange(other.this_coro, nullptr) }, this_promise{ exchange(other.this_promise, nullptr) } { 
		
	}

	~bug_task()
	{
		if (this_coro)
			this_coro.destroy();
	}

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

	handle await_suspend(handle waiter) noexcept
	{
		return this_coro;
	}

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

	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_{ static_cast<bug_task&&>(t) } {}

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

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.awaiter_->waiter.resume();
		}
	};

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 }, static_cast<bug_task&&>(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 }; }

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;
	unsigned count_ = 0;
};

// test case

bug_task bug_spawned_task(int id, int inc)
{
	co_return;
}

struct A {
    A();
};

void throwing_fn(bug_spawner& s) {
	s.spawn(bug_spawned_task(1, 2));
    throw A{};
}

// Check that the coroutine frame of bug_spawned_task are allocated from operator new.
// CHECK: define{{.*}}@_Z11throwing_fnR11bug_spawner
// CHECK-NOT: alloc
// CHECK: %[[CALL:.+]] = {{.*}}@_Znwm(i64{{.*}} 24)
// CHECK: store ptr @_Z16bug_spawned_taskii.resume, ptr %[[CALL]]