https://gcc.gnu.org/g:a085455306ec4aa15a11efacd994968bf951f7b6
commit r17-2063-ga085455306ec4aa15a11efacd994968bf951f7b6 Author: Jason Merrill <[email protected]> Date: Wed Jul 1 11:28:18 2026 -0400 c++: co_await and structured bindings [PR124584] Here the internal guard variables that control whether to clean up the structured binding variables were not living across the call to co_await because they weren't promoted into the coroutine frame. The underlying problem is using a TARGET_EXPR, which lives only for the full-expression, to hold a value that needs to live as long as the variable itself. So get_temp_regvar seems like a better fit. We also need to manually pushdecl the guard variable so that it's visible to register_local_var_uses. It might be better to use the wrap_temporary_cleanups mechanism for the main variable, as we do for normal variables. But let's go with the simple fix for now. PR c++/124584 gcc/cp/ChangeLog: * decl.cc (cp_finish_decl): Use get_temp_regvar for decomp guards. gcc/testsuite/ChangeLog: * g++.dg/coroutines/torture/tuple-decomp-pr124584.C: New test. Diff: --- gcc/cp/decl.cc | 15 +++--- .../coroutines/torture/tuple-decomp-pr124584.C | 54 ++++++++++++++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index dbe4fa44ce52..c2a75c669c7b 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -10108,10 +10108,14 @@ cp_finish_decl (tree decl, tree init, bool init_const_expr_p, tree guard = NULL_TREE; if (cleanups || cleanup) { - guard = get_internal_target_expr (boolean_false_node); - add_stmt (guard); - guard = TARGET_EXPR_SLOT (guard); + /* Since the CLEANUP_STMT will refer to the guard, we need it + to be a variable with the same lifetime. ??? It might be + better to use wrap_temporary_cleanups. */ + guard = get_temp_regvar (boolean_type_node, boolean_false_node); + /* And make sure register_local_var_uses sees it. */ + pushdecl (guard); } + tree sl = push_stmt_list (); initialize_local_var (decl, init, true); if (guard) @@ -10137,9 +10141,8 @@ cp_finish_decl (tree decl, tree init, bool init_const_expr_p, popped that all, so push those extra cleanups around the whole sequence with a guard variable. */ gcc_assert (TREE_CODE (sl) == STATEMENT_LIST); - guard = get_internal_target_expr (integer_zero_node); - add_stmt (guard); - guard = TARGET_EXPR_SLOT (guard); + guard = get_temp_regvar (integer_type_node, integer_zero_node); + pushdecl (guard); for (unsigned i = 0; i < n_extra_cleanups; ++i) { tree_stmt_iterator tsi = tsi_last (sl); diff --git a/gcc/testsuite/g++.dg/coroutines/torture/tuple-decomp-pr124584.C b/gcc/testsuite/g++.dg/coroutines/torture/tuple-decomp-pr124584.C new file mode 100644 index 000000000000..8679d52bf09c --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/tuple-decomp-pr124584.C @@ -0,0 +1,54 @@ +#include <coroutine> +#include <tuple> + +static int g_dtor_count = 0; + +struct guard { + bool live_ = true; + ~guard() { if (live_) ++g_dtor_count; } + guard() = default; + guard(guard&& o) noexcept + : live_(o.live_) {o.live_ = false;} +}; + +static std::coroutine_handle<> g_pending; + +struct yield_aw { + bool await_ready() const noexcept { return false; } + void await_suspend(std::coroutine_handle<> h) noexcept + { g_pending = h; } + void await_resume() const noexcept {} +}; + +struct task { + struct promise_type { + task get_return_object() noexcept { + return {std::coroutine_handle< + promise_type>::from_promise(*this)}; + } + std::suspend_never initial_suspend() noexcept { return {}; } + std::suspend_always final_suspend() noexcept { return {}; } + void return_void() noexcept {} + void unhandled_exception() { __builtin_abort(); } + }; + std::coroutine_handle<promise_type> h_; + ~task() { if (h_) h_.destroy(); } +}; + +std::tuple<guard> make_guarded_tuple() { return {guard{}}; } + +task test_fn() { + auto [g] = make_guarded_tuple(); // not co_await + co_await yield_aw{}; // binding crosses suspend + (void)g; +} + +int main() { + auto t = test_fn(); + auto h = g_pending; + g_pending = nullptr; + h.resume(); + __builtin_printf("dtor_count = %d %s\n", + g_dtor_count, g_dtor_count > 0 ? "OK" : "BUG"); + return g_dtor_count > 0 ? 0 : 1; +}
