]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++, coroutines: Use decltype(auto) for the g_r_o.
authorIain Sandoe <iain@sandoe.co.uk>
Sun, 11 May 2025 19:36:58 +0000 (20:36 +0100)
committerIain Sandoe <iain@sandoe.co.uk>
Tue, 29 Jul 2025 15:02:55 +0000 (16:02 +0100)
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 <iain@sandoe.co.uk>
(cherry picked from commit e71a6e002c6650a7a7be99277120d3e59ecb78a3)

gcc/cp/coroutines.cc
gcc/testsuite/g++.dg/coroutines/pr115908.C

index 6280b1d1ad43ee3ae9045fe4d4488ee527e04be8..39372239c8ded75a8e1ea27ae8d87b7be1d119be 100644 (file)
@@ -5137,8 +5137,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"
@@ -5176,7 +5179,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);
@@ -5198,7 +5201,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)
     {
index ac27d916de2b6e2ad8897de05d9647fecdc60bad..a40cece11438becbd8e37aa273abb49cc6ef55ca 100644 (file)
@@ -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
 
 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;
 }