https://gcc.gnu.org/g:e71a6e002c6650a7a7be99277120d3e59ecb78a3
commit r16-774-ge71a6e002c6650a7a7be99277120d3e59ecb78a3 Author: Iain Sandoe <i...@sandoe.co.uk> Date: Sun May 11 20:36:58 2025 +0100 c++, coroutines: Use decltype(auto) for the g_r_o. The revised wording for coroutines, uses decltype(auto) for the type of the get return object, which preserves references. It is quite reasonable for a coroutine body implementation to complete before control is returned to the ramp - and in that case we would be creating the ramp return object from an already- deleted promise object. Jason observes that this is a terrible situation and we should seek a resolution to it via core. Since the test added here explicitly performs the unsafe action dscribed above we expect it to fail (until a resolution is found). gcc/cp/ChangeLog: * coroutines.cc (cp_coroutine_transform::build_ramp_function): Use decltype(auto) to determine the type of the temporary get_return_object. gcc/testsuite/ChangeLog: * g++.dg/coroutines/pr115908.C: Count promise construction and destruction. Run the test and XFAIL it. Signed-off-by: Iain Sandoe <i...@sandoe.co.uk> Diff: --- gcc/cp/coroutines.cc | 12 +++-- gcc/testsuite/g++.dg/coroutines/pr115908.C | 86 ++++++++++++++++++++++-------- 2 files changed, 72 insertions(+), 26 deletions(-) diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc index bc5fb9381dbe..5c4133a42b7e 100644 --- a/gcc/cp/coroutines.cc +++ b/gcc/cp/coroutines.cc @@ -5120,8 +5120,11 @@ cp_coroutine_transform::build_ramp_function () /* Check for a bad get return object type. [dcl.fct.def.coroutine] / 7 requires: The expression promise.get_return_object() is used to initialize the - returned reference or prvalue result object ... */ - tree gro_type = TREE_TYPE (get_ro); + returned reference or prvalue result object ... + When we use a local to hold this, it is decltype(auto). */ + tree gro_type + = finish_decltype_type (get_ro, /*id_expression_or_member_access_p*/false, + tf_warning_or_error); if (VOID_TYPE_P (gro_type) && !void_ramp_p) { error_at (fn_start, "no viable conversion from %<void%> provided by" @@ -5159,7 +5162,7 @@ cp_coroutine_transform::build_ramp_function () = coro_build_and_push_artificial_var (loc, "_Coro_gro", gro_type, orig_fn_decl, NULL_TREE); - r = cp_build_init_expr (coro_gro, get_ro); + r = cp_build_init_expr (coro_gro, STRIP_REFERENCE_REF (get_ro)); finish_expr_stmt (r); tree coro_gro_cleanup = cxx_maybe_build_cleanup (coro_gro, tf_warning_or_error); @@ -5181,7 +5184,8 @@ cp_coroutine_transform::build_ramp_function () /* The ramp is done, we just need the return statement, which we build from the return object we constructed before we called the function body. */ - finish_return_stmt (void_ramp_p ? NULL_TREE : coro_gro); + r = void_ramp_p ? NULL_TREE : convert_from_reference (coro_gro); + finish_return_stmt (r); if (flag_exceptions) { diff --git a/gcc/testsuite/g++.dg/coroutines/pr115908.C b/gcc/testsuite/g++.dg/coroutines/pr115908.C index ac27d916de2b..a40cece11438 100644 --- a/gcc/testsuite/g++.dg/coroutines/pr115908.C +++ b/gcc/testsuite/g++.dg/coroutines/pr115908.C @@ -1,3 +1,16 @@ +// { dg-do run } + +// With the changes to deal with CWG2563 (and PR119916) we now use the +// referenced promise in the return expression. It is quite reasonable +// for a body implementation to complete before control is returned to +// the ramp - and in that case we would be creating the ramp return object +// from an already-deleted promise object. +// This is recognised to be a poor situation and resolution via a core +// issue is planned. + +// In this test we explicitly trigger the circumstance mentioned above. +// { dg-xfail-run-if "" { *-*-* } } + #include <coroutine> #ifdef OUTPUT @@ -6,23 +19,25 @@ struct Promise; -bool promise_live = false; +int promise_life = 0; struct Handle : std::coroutine_handle<Promise> { + Handle(Promise &p) : std::coroutine_handle<Promise>(Handle::from_promise(p)) { - if (!promise_live) - __builtin_abort (); #ifdef OUTPUT - std::cout << "Handle(Promise &)\n"; + std::cout << "Handle(Promise &) " << promise_life << std::endl; #endif - } - Handle(Promise &&p) : std::coroutine_handle<Promise>(Handle::from_promise(p)) { - if (!promise_live) + if (promise_life <= 0) __builtin_abort (); + } + + Handle(Promise &&p) : std::coroutine_handle<Promise>(Handle::from_promise(p)) { #ifdef OUTPUT - std::cout << "Handle(Promise &&)\n"; + std::cout << "Handle(Promise &&) " << promise_life << std::endl; #endif - } + if (promise_life <= 0) + __builtin_abort (); + } using promise_type = Promise; }; @@ -30,46 +45,73 @@ struct Handle : std::coroutine_handle<Promise> { struct Promise { Promise() { #ifdef OUTPUT - std::cout << "Promise()\n"; + std::cout << "Promise()" << std::endl; +#endif + promise_life++; + } + + Promise(Promise& p){ +#ifdef OUTPUT + std::cout << "Promise(Promise&)" << std::endl; #endif - promise_live = true; + promise_life++; } + ~Promise() { #ifdef OUTPUT - std::cout << "~Promise()\n"; + std::cout << "~Promise()" << std::endl; #endif - if (!promise_live) + if (promise_life <= 0) __builtin_abort (); - promise_live = false; + promise_life--; } + Promise& get_return_object() noexcept { #ifdef OUTPUT - std::cout << "get_return_object()\n"; + std::cout << "get_return_object() " << promise_life << std::endl; #endif - if (!promise_live) + if (promise_life <= 0) __builtin_abort (); return *this; } - std::suspend_never initial_suspend() const noexcept { return {}; } - std::suspend_never final_suspend() const noexcept { return {}; } + + std::suspend_never initial_suspend() const noexcept { +#ifdef OUTPUT + std::cout << "initial_suspend()" << std::endl; +#endif + return {}; + } + std::suspend_never final_suspend() const noexcept { +#ifdef OUTPUT + std::cout << "final_suspend()" << std::endl; +#endif + return {}; + } void return_void() const noexcept { - if (!promise_live) + if (!promise_life) __builtin_abort (); #ifdef OUTPUT - std::cout << "return_void()\n"; + std::cout << "return_void()" << std::endl; #endif } void unhandled_exception() const noexcept {} }; Handle Coro() { + +#ifdef OUTPUT + std::cout << "Coro()" << std::endl; +#endif co_return; } int main() { - Coro(); - if (promise_live) + Coro(); +#ifdef OUTPUT + std::cout << "done Coro()" << std::endl; +#endif + if (promise_life) __builtin_abort (); return 0; }