public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] coroutines : Do not accept throwing final await expressions  [PR95616].
@ 2021-03-04 19:54 Iain Sandoe
  2021-03-04 20:05 ` Nathan Sidwell
  0 siblings, 1 reply; 3+ messages in thread
From: Iain Sandoe @ 2021-03-04 19:54 UTC (permalink / raw)
  To: GCC Patches; +Cc: Nathan Sidwell

Hi,

From the PR:

The wording of [dcl.fct.def.coroutine]/15 states:
 * The expression co_await promise.final_suspend() shall not be
   potentially-throwing ([except.spec]).

See http://eel.is/c++draft/dcl.fct.def.coroutine#15
and http://eel.is/c++draft/except.spec#6

ie. all of the following must be declared noexcept (if they form part of the await-expression):
- promise_type::final_suspend()
- finalSuspendObj.operator co_await()
- finalSuspendAwaiter.await_ready()
- finalSuspendAwaiter.await_suspend()
- finalSuspendAwaiter.await_resume()
- finalSuspedObj destructor
- finalSuspendAwaiter destructor

This implements the checks for these cases and rejects such code with
a diagnostic.

[ accepts invalid ]
tested on x86_64-darwin, x86_64-linux-gnu,
OK for master / 10.x?
thanks
Iain


gcc/cp/ChangeLog:

	PR c++/95616
	* coroutines.cc (coro_diagnose_throwing_fn): New helper.
	(coro_diagnose_throwing_final_aw_expr): New helper.
	(build_co_await): Diagnose throwing final await expression
	components.
	(build_init_or_final_await): Diagnose a throwing promise
	final_suspend() call.

gcc/testsuite/ChangeLog:

	PR c++/95616
	* g++.dg/coroutines/pr95616-a.C: New test.
	* g++.dg/coroutines/pr95616-b.C: New test.
	* g++.dg/coroutines/pr95616-c.C: New test.
	* g++.dg/coroutines/pr95616-d.C: New test.
	* g++.dg/coroutines/pr95616-e.C: New test.
	* g++.dg/coroutines/pr95616-f.C: New test.
	* g++.dg/coroutines/pr95616-g.C: New test.
---
 gcc/cp/coroutines.cc                        | 85 +++++++++++++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-a.C | 51 +++++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-b.C | 51 +++++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-c.C | 51 +++++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-d.C | 51 +++++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-e.C | 51 +++++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-f.C | 51 +++++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-g.C | 51 +++++++++++++
 8 files changed, 442 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-a.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-b.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-c.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-d.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-e.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-f.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-g.C

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index ca36c8b8b41..f79ac60dc77 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -793,6 +793,43 @@ get_awaitable_var (suspend_point_kind suspend_kind, tree v_type)
   return ret;
 }
 
+/* Helpers to diagnose missing noexcept on final await expressions.  */
+
+static bool
+coro_diagnose_throwing_fn (tree fndecl)
+{
+  if (!TYPE_NOTHROW_P (TREE_TYPE (fndecl)))
+    {
+      location_t f_loc = cp_expr_loc_or_loc (fndecl,
+					     DECL_SOURCE_LOCATION (fndecl));
+      error_at (f_loc, "the expression %qE is required to be non-throwing",
+		fndecl);
+      inform (f_loc, "must be declared with %<noexcept(true)%>");
+      return true;
+    }
+  return false;
+}
+
+static bool
+coro_diagnose_throwing_final_aw_expr (tree expr)
+{
+  tree t = TARGET_EXPR_INITIAL (expr);
+  tree fn = NULL_TREE;
+  if (TREE_CODE (t) == CALL_EXPR)
+    fn = CALL_EXPR_FN(t);
+  else if (TREE_CODE (t) == AGGR_INIT_EXPR)
+    fn = AGGR_INIT_EXPR_FN (t);
+  else if (TREE_CODE (t) == CONSTRUCTOR)
+    return false;
+  else
+    {
+      gcc_checking_assert (0 && "unhandled expression type");
+      return false;
+    }
+  fn = TREE_OPERAND (fn, 0);
+  return coro_diagnose_throwing_fn (fn);
+}
+
 /*  This performs [expr.await] bullet 3.3 and validates the interface obtained.
     It is also used to build the initial and final suspend points.
 
@@ -815,6 +852,28 @@ build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind)
       /* If no viable functions are found, o is a.  */
       if (!o || o == error_mark_node)
 	o = a;
+      else if (suspend_kind == FINAL_SUSPEND_POINT)
+	{
+	  /* We found an overload for co_await(), diagnose throwing cases.  */
+	  if (TREE_CODE (o) == TARGET_EXPR
+	      && coro_diagnose_throwing_final_aw_expr (o))
+	    return error_mark_node;
+
+	  /* We now know that the final suspend object is distinct from the
+	     final awaiter, so check for a non-throwing DTOR where needed.  */
+	  tree a_type = TREE_TYPE (a);
+	  if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (a_type))
+	    {
+	      tree dummy
+		= build_special_member_call (a, complete_dtor_identifier,
+					     NULL, a_type, LOOKUP_NORMAL,
+					     tf_none);
+	      dummy = dummy ? TREE_OPERAND (CALL_EXPR_FN (dummy), 0)
+			    : NULL_TREE;
+	      if (dummy && coro_diagnose_throwing_fn (dummy))
+		return error_mark_node;
+	    }
+	}
     }
   else
     o = a; /* This is most likely about to fail anyway.  */
@@ -958,6 +1017,27 @@ build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind)
   if (!awrs_func || !awrs_call || awrs_call == error_mark_node)
     return error_mark_node;
 
+  if (suspend_kind == FINAL_SUSPEND_POINT)
+    {
+      if (coro_diagnose_throwing_fn (awrd_func))
+	return error_mark_node;
+      if (coro_diagnose_throwing_fn (awsp_func))
+	return error_mark_node;
+      if (coro_diagnose_throwing_fn (awrs_func))
+	return error_mark_node;
+      if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (o_type))
+	{
+	  tree dummy
+	    = build_special_member_call (e_proxy, complete_dtor_identifier,
+					 NULL, o_type, LOOKUP_NORMAL,
+					 tf_none);
+	  dummy = dummy ? TREE_OPERAND (CALL_EXPR_FN (dummy), 0)
+			: NULL_TREE;
+	  if (dummy && coro_diagnose_throwing_fn (dummy))
+	    return error_mark_node;
+	}
+    }
+
   /* We now have three call expressions, in terms of the promise, handle and
      'e' proxies.  Save them in the await expression for later expansion.  */
 
@@ -2538,6 +2618,11 @@ build_init_or_final_await (location_t loc, bool is_final)
     = coro_build_promise_expression (current_function_decl, NULL, suspend_alt,
 				     loc, NULL, /*musthave=*/true);
 
+  /* Check for noexcept on the final_suspend call.  */
+  if (is_final && setup_call != error_mark_node
+      && coro_diagnose_throwing_final_aw_expr (setup_call))
+    return error_mark_node;
+
   /* So build the co_await for this */
   /* For initial/final suspends the call is "a" per [expr.await] 3.2.  */
   return build_co_await (loc, setup_call, (is_final ? FINAL_SUSPEND_POINT
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-a.C b/gcc/testsuite/g++.dg/coroutines/pr95616-a.C
new file mode 100644
index 00000000000..e500b6ea636
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-a.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(true) { }
+  bool await_ready() const noexcept(true) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(true) { }
+  int await_resume() const noexcept(true) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() { return {3}; } // NOTE: not declared noexcept
+  // { dg-error {the expression 'task::promise_type::final_suspend' is required to be non-throwing} "" { target *-*-* } .-1 }
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-b.C b/gcc/testsuite/g++.dg/coroutines/pr95616-b.C
new file mode 100644
index 00000000000..8d0d0b318ca
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-b.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(false) { }
+  bool await_ready() const noexcept(false) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(false) { }
+  int await_resume() const noexcept(false) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(false) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(false) { 
+  // { dg-error {the expression 'finalSuspendObj::operator co_await' is required to be non-throwing} "" { target *-*-* } .-1 }
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; }
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-c.C b/gcc/testsuite/g++.dg/coroutines/pr95616-c.C
new file mode 100644
index 00000000000..6ad251986ec
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-c.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(false) { }
+  bool await_ready() const noexcept(false) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(false) { }
+  int await_resume() const noexcept(false) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(false) {} 
+  // { dg-error {the expression 'finalSuspendObj::~finalSuspendObj' is required to be non-throwing} "" { target *-*-* } .-1 }
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; } // NOTE: not declared noexcept
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-d.C b/gcc/testsuite/g++.dg/coroutines/pr95616-d.C
new file mode 100644
index 00000000000..7da1f6a9658
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-d.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(false) { }
+  bool await_ready() const noexcept(false) { return false; }
+  // { dg-error {the expression 'finalSuspendAwaiter::await_ready' is required to be non-throwing} "" { target *-*-* } .-1 }
+  void await_suspend(std::coroutine_handle<>) const noexcept(false) { }
+  int await_resume() const noexcept(false) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; }
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-e.C b/gcc/testsuite/g++.dg/coroutines/pr95616-e.C
new file mode 100644
index 00000000000..ef6a160a5c4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-e.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(false) { }
+  bool await_ready() const noexcept(true) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(false) { }
+  // { dg-error {the expression 'finalSuspendAwaiter::await_suspend' is required to be non-throwing} "" { target *-*-* } .-1 }
+  int await_resume() const noexcept(false) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; }
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-f.C b/gcc/testsuite/g++.dg/coroutines/pr95616-f.C
new file mode 100644
index 00000000000..930c1a7e6a1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-f.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(false) { }
+  bool await_ready() const noexcept(true) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(true) { }
+  int await_resume() const noexcept(false) { return x; }
+  // { dg-error {the expression 'finalSuspendAwaiter::await_resume' is required to be non-throwing} "" { target *-*-* } .-1 }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; }
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-g.C b/gcc/testsuite/g++.dg/coroutines/pr95616-g.C
new file mode 100644
index 00000000000..e7481711c5e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-g.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(false) { }
+  // { dg-error {the expression 'finalSuspendAwaiter::~finalSuspendAwaiter' is required to be non-throwing} "" { target *-*-* } .-1 }
+  bool await_ready() const noexcept(true) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(true) { }
+  int await_resume() const noexcept(true) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; } 
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
-- 
2.24.1


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] coroutines : Do not accept throwing final await expressions [PR95616].
  2021-03-04 19:54 [PATCH] coroutines : Do not accept throwing final await expressions [PR95616] Iain Sandoe
@ 2021-03-04 20:05 ` Nathan Sidwell
  2021-03-05 17:03   ` Iain Sandoe
  0 siblings, 1 reply; 3+ messages in thread
From: Nathan Sidwell @ 2021-03-04 20:05 UTC (permalink / raw)
  To: Iain Sandoe, GCC Patches

On 3/4/21 2:54 PM, Iain Sandoe wrote:
> Hi,
> 
>  From the PR:
> 
> The wording of [dcl.fct.def.coroutine]/15 states:
>   * The expression co_await promise.final_suspend() shall not be
>     potentially-throwing ([except.spec]).
> 
> See http://eel.is/c++draft/dcl.fct.def.coroutine#15
> and http://eel.is/c++draft/except.spec#6
> 
> ie. all of the following must be declared noexcept (if they form part of the await-expression):
> - promise_type::final_suspend()
> - finalSuspendObj.operator co_await()
> - finalSuspendAwaiter.await_ready()
> - finalSuspendAwaiter.await_suspend()
> - finalSuspendAwaiter.await_resume()
> - finalSuspedObj destructor
> - finalSuspendAwaiter destructor
> 
> This implements the checks for these cases and rejects such code with
> a diagnostic.
> 
> [ accepts invalid ]
> tested on x86_64-darwin, x86_64-linux-gnu,
> OK for master / 10.x?
> thanks
> Iain
> 
> 
> gcc/cp/ChangeLog:
> 
> 	PR c++/95616
> 	* coroutines.cc (coro_diagnose_throwing_fn): New helper.
> 	(coro_diagnose_throwing_final_aw_expr): New helper.
> 	(build_co_await): Diagnose throwing final await expression
> 	components.
> 	(build_init_or_final_await): Diagnose a throwing promise
> 	final_suspend() call.
> 

ok.  Does this DTRT in the presence of -fno-exceptions?  (i.e. use of 
that flag means you don't have to decorate everything with noexcept)

nathan

-- 
Nathan Sidwell

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] coroutines : Do not accept throwing final await expressions [PR95616].
  2021-03-04 20:05 ` Nathan Sidwell
@ 2021-03-05 17:03   ` Iain Sandoe
  0 siblings, 0 replies; 3+ messages in thread
From: Iain Sandoe @ 2021-03-05 17:03 UTC (permalink / raw)
  To: Nathan Sidwell; +Cc: GCC Patches

[-- Attachment #1: Type: text/plain, Size: 1648 bytes --]

Nathan Sidwell <nathan@acm.org> wrote:

> On 3/4/21 2:54 PM, Iain Sandoe wrote:
>> Hi,
>> From the PR:
>> The wording of [dcl.fct.def.coroutine]/15 states:
>>  * The expression co_await promise.final_suspend() shall not be
>>    potentially-throwing ([except.spec]).
>> See http://eel.is/c++draft/dcl.fct.def.coroutine#15
>> and http://eel.is/c++draft/except.spec#6
>> ie. all of the following must be declared noexcept (if they form part of  
>> the await-expression):
>> - promise_type::final_suspend()
>> - finalSuspendObj.operator co_await()
>> - finalSuspendAwaiter.await_ready()
>> - finalSuspendAwaiter.await_suspend()
>> - finalSuspendAwaiter.await_resume()
>> - finalSuspedObj destructor
>> - finalSuspendAwaiter destructor
>> This implements the checks for these cases and rejects such code with
>> a diagnostic.
>> [ accepts invalid ]
>> tested on x86_64-darwin, x86_64-linux-gnu,
>> OK for master / 10.x?
>> thanks
>> Iain
>> gcc/cp/ChangeLog:
>> 	PR c++/95616
>> 	* coroutines.cc (coro_diagnose_throwing_fn): New helper.
>> 	(coro_diagnose_throwing_final_aw_expr): New helper.
>> 	(build_co_await): Diagnose throwing final await expression
>> 	components.
>> 	(build_init_or_final_await): Diagnose a throwing promise
>> 	final_suspend() call.
>
> ok.  Does this DTRT in the presence of -fno-exceptions?

thanks for catching this…

>  (i.e. use of that flag means you don't have to decorate everything with noexcept)

As discussed on IRC, I updated this to gate the diagnostics on  
flag_exceptions (and added three more tests to cover that circumstance).

pushed to master, (for the record, updated version attached),
thanks,
Iain



[-- Attachment #2: 0001-coroutines-Do-not-accept-throwing-final-await-expres.patch --]
[-- Type: application/octet-stream, Size: 24193 bytes --]

From 9ee91079fd5879cba046e452ab5593372166b2ab Mon Sep 17 00:00:00 2001
From: Iain Sandoe <iain@sandoe.co.uk>
Date: Mon, 15 Feb 2021 17:11:31 +0000
Subject: [PATCH] coroutines : Do not accept throwing final await expressions
 [PR95616].

From the PR:

The wording of [dcl.fct.def.coroutine]/15 states:
 * The expression co_await promise.final_suspend() shall not be
   potentially-throwing ([except.spec]).

See http://eel.is/c++draft/dcl.fct.def.coroutine#15
and http://eel.is/c++draft/except.spec#6

ie. all of the following must be declared noexcept (if they form part of the await-expression):
- promise_type::final_suspend()
- finalSuspendObj.operator co_await()
- finalSuspendAwaiter.await_ready()
- finalSuspendAwaiter.await_suspend()
- finalSuspendAwaiter.await_resume()
- finalSuspedObj destructor
- finalSuspendAwaiter destructor

This implements the checks for these cases and rejects such code with
a diagnostic if exceptions are enabled.

gcc/cp/ChangeLog:

	PR c++/95616
	* coroutines.cc (coro_diagnose_throwing_fn): New helper.
	(coro_diagnose_throwing_final_aw_expr): New helper.
	(build_co_await): Diagnose throwing final await expression
	components.
	(build_init_or_final_await): Diagnose a throwing promise
	final_suspend() call.

gcc/testsuite/ChangeLog:

	PR c++/95616
	* g++.dg/coroutines/pr95616-0-no-exceptions.C: New test.
	* g++.dg/coroutines/pr95616-0.C: New test.
	* g++.dg/coroutines/pr95616-1-no-exceptions.C: New test.
	* g++.dg/coroutines/pr95616-1.C: New test.
	* g++.dg/coroutines/pr95616-2.C: New test.
	* g++.dg/coroutines/pr95616-3-no-exceptions.C: New test.
	* g++.dg/coroutines/pr95616-3.C: New test.
	* g++.dg/coroutines/pr95616-4.C: New test.
	* g++.dg/coroutines/pr95616-5.C: New test.
	* g++.dg/coroutines/pr95616-6.C: New test.
---
 gcc/cp/coroutines.cc                          | 85 +++++++++++++++++++
 .../coroutines/pr95616-0-no-exceptions.C      | 50 +++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-0.C   | 51 +++++++++++
 .../coroutines/pr95616-1-no-exceptions.C      | 51 +++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-1.C   | 51 +++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-2.C   | 51 +++++++++++
 .../coroutines/pr95616-3-no-exceptions.C      | 50 +++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-3.C   | 51 +++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-4.C   | 51 +++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-5.C   | 51 +++++++++++
 gcc/testsuite/g++.dg/coroutines/pr95616-6.C   | 51 +++++++++++
 11 files changed, 593 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-0-no-exceptions.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-0.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-1-no-exceptions.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-1.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-2.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-3-no-exceptions.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-3.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-4.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-5.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr95616-6.C

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index 3f88b077d08..ef50a5f5fa3 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -793,6 +793,43 @@ get_awaitable_var (suspend_point_kind suspend_kind, tree v_type)
   return ret;
 }
 
+/* Helpers to diagnose missing noexcept on final await expressions.  */
+
+static bool
+coro_diagnose_throwing_fn (tree fndecl)
+{
+  if (!TYPE_NOTHROW_P (TREE_TYPE (fndecl)))
+    {
+      location_t f_loc = cp_expr_loc_or_loc (fndecl,
+					     DECL_SOURCE_LOCATION (fndecl));
+      error_at (f_loc, "the expression %qE is required to be non-throwing",
+		fndecl);
+      inform (f_loc, "must be declared with %<noexcept(true)%>");
+      return true;
+    }
+  return false;
+}
+
+static bool
+coro_diagnose_throwing_final_aw_expr (tree expr)
+{
+  tree t = TARGET_EXPR_INITIAL (expr);
+  tree fn = NULL_TREE;
+  if (TREE_CODE (t) == CALL_EXPR)
+    fn = CALL_EXPR_FN(t);
+  else if (TREE_CODE (t) == AGGR_INIT_EXPR)
+    fn = AGGR_INIT_EXPR_FN (t);
+  else if (TREE_CODE (t) == CONSTRUCTOR)
+    return false;
+  else
+    {
+      gcc_checking_assert (0 && "unhandled expression type");
+      return false;
+    }
+  fn = TREE_OPERAND (fn, 0);
+  return coro_diagnose_throwing_fn (fn);
+}
+
 /*  This performs [expr.await] bullet 3.3 and validates the interface obtained.
     It is also used to build the initial and final suspend points.
 
@@ -815,6 +852,28 @@ build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind)
       /* If no viable functions are found, o is a.  */
       if (!o || o == error_mark_node)
 	o = a;
+      else if (flag_exceptions && suspend_kind == FINAL_SUSPEND_POINT)
+	{
+	  /* We found an overload for co_await(), diagnose throwing cases.  */
+	  if (TREE_CODE (o) == TARGET_EXPR
+	      && coro_diagnose_throwing_final_aw_expr (o))
+	    return error_mark_node;
+
+	  /* We now know that the final suspend object is distinct from the
+	     final awaiter, so check for a non-throwing DTOR where needed.  */
+	  tree a_type = TREE_TYPE (a);
+	  if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (a_type))
+	    {
+	      tree dummy
+		= build_special_member_call (a, complete_dtor_identifier,
+					     NULL, a_type, LOOKUP_NORMAL,
+					     tf_none);
+	      dummy = dummy ? TREE_OPERAND (CALL_EXPR_FN (dummy), 0)
+			    : NULL_TREE;
+	      if (dummy && coro_diagnose_throwing_fn (dummy))
+		return error_mark_node;
+	    }
+	}
     }
   else
     o = a; /* This is most likely about to fail anyway.  */
@@ -958,6 +1017,27 @@ build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind)
   if (!awrs_func || !awrs_call || awrs_call == error_mark_node)
     return error_mark_node;
 
+  if (flag_exceptions && suspend_kind == FINAL_SUSPEND_POINT)
+    {
+      if (coro_diagnose_throwing_fn (awrd_func))
+	return error_mark_node;
+      if (coro_diagnose_throwing_fn (awsp_func))
+	return error_mark_node;
+      if (coro_diagnose_throwing_fn (awrs_func))
+	return error_mark_node;
+      if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (o_type))
+	{
+	  tree dummy
+	    = build_special_member_call (e_proxy, complete_dtor_identifier,
+					 NULL, o_type, LOOKUP_NORMAL,
+					 tf_none);
+	  dummy = dummy ? TREE_OPERAND (CALL_EXPR_FN (dummy), 0)
+			: NULL_TREE;
+	  if (dummy && coro_diagnose_throwing_fn (dummy))
+	    return error_mark_node;
+	}
+    }
+
   /* We now have three call expressions, in terms of the promise, handle and
      'e' proxies.  Save them in the await expression for later expansion.  */
 
@@ -2538,6 +2618,11 @@ build_init_or_final_await (location_t loc, bool is_final)
     = coro_build_promise_expression (current_function_decl, NULL, suspend_alt,
 				     loc, NULL, /*musthave=*/true);
 
+  /* Check for noexcept on the final_suspend call.  */
+  if (flag_exceptions && is_final && setup_call != error_mark_node
+      && coro_diagnose_throwing_final_aw_expr (setup_call))
+    return error_mark_node;
+
   /* So build the co_await for this */
   /* For initial/final suspends the call is "a" per [expr.await] 3.2.  */
   return build_co_await (loc, setup_call, (is_final ? FINAL_SUSPEND_POINT
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-0-no-exceptions.C b/gcc/testsuite/g++.dg/coroutines/pr95616-0-no-exceptions.C
new file mode 100644
index 00000000000..7ce811dc569
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-0-no-exceptions.C
@@ -0,0 +1,50 @@
+//  { dg-additional-options "-fsyntax-only -fno-exceptions" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(true) { }
+  bool await_ready() const noexcept(true) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(true) { }
+  int await_resume() const noexcept(true) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() { return {3}; } // NOTE: not declared noexcept
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// OK when exceptions are disabled
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-0.C b/gcc/testsuite/g++.dg/coroutines/pr95616-0.C
new file mode 100644
index 00000000000..e500b6ea636
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-0.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(true) { }
+  bool await_ready() const noexcept(true) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(true) { }
+  int await_resume() const noexcept(true) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() { return {3}; } // NOTE: not declared noexcept
+  // { dg-error {the expression 'task::promise_type::final_suspend' is required to be non-throwing} "" { target *-*-* } .-1 }
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-1-no-exceptions.C b/gcc/testsuite/g++.dg/coroutines/pr95616-1-no-exceptions.C
new file mode 100644
index 00000000000..48981e6e51d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-1-no-exceptions.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only -fno-exceptions" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(true) { }
+  bool await_ready() const noexcept(true) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(true) { }
+  int await_resume() const noexcept(true) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(false) // NOTE: not declared noexcept
+   { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; }
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// OK when exceptions are disabled.
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-1.C b/gcc/testsuite/g++.dg/coroutines/pr95616-1.C
new file mode 100644
index 00000000000..c3989804e6b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-1.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(true) { }
+  bool await_ready() const noexcept(true) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(true) { }
+  int await_resume() const noexcept(true) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(false) { 
+  // { dg-error {the expression 'finalSuspendObj::operator co_await' is required to be non-throwing} "" { target *-*-* } .-1 }
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; }
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-2.C b/gcc/testsuite/g++.dg/coroutines/pr95616-2.C
new file mode 100644
index 00000000000..6ad251986ec
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-2.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(false) { }
+  bool await_ready() const noexcept(false) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(false) { }
+  int await_resume() const noexcept(false) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(false) {} 
+  // { dg-error {the expression 'finalSuspendObj::~finalSuspendObj' is required to be non-throwing} "" { target *-*-* } .-1 }
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; } // NOTE: not declared noexcept
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-3-no-exceptions.C b/gcc/testsuite/g++.dg/coroutines/pr95616-3-no-exceptions.C
new file mode 100644
index 00000000000..1dcd426c608
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-3-no-exceptions.C
@@ -0,0 +1,50 @@
+//  { dg-additional-options "-fsyntax-only -fno-exceptions" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(false) { }
+  bool await_ready() const noexcept(false) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(false) { }
+  int await_resume() const noexcept(false) { return x; }  // NOTE: not declared noexcept
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; }
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// OK when exceptions are disabled
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-3.C b/gcc/testsuite/g++.dg/coroutines/pr95616-3.C
new file mode 100644
index 00000000000..7da1f6a9658
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-3.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(false) { }
+  bool await_ready() const noexcept(false) { return false; }
+  // { dg-error {the expression 'finalSuspendAwaiter::await_ready' is required to be non-throwing} "" { target *-*-* } .-1 }
+  void await_suspend(std::coroutine_handle<>) const noexcept(false) { }
+  int await_resume() const noexcept(false) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; }
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-4.C b/gcc/testsuite/g++.dg/coroutines/pr95616-4.C
new file mode 100644
index 00000000000..ef6a160a5c4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-4.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(false) { }
+  bool await_ready() const noexcept(true) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(false) { }
+  // { dg-error {the expression 'finalSuspendAwaiter::await_suspend' is required to be non-throwing} "" { target *-*-* } .-1 }
+  int await_resume() const noexcept(false) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; }
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-5.C b/gcc/testsuite/g++.dg/coroutines/pr95616-5.C
new file mode 100644
index 00000000000..930c1a7e6a1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-5.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(false) { }
+  bool await_ready() const noexcept(true) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(true) { }
+  int await_resume() const noexcept(false) { return x; }
+  // { dg-error {the expression 'finalSuspendAwaiter::await_resume' is required to be non-throwing} "" { target *-*-* } .-1 }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; }
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr95616-6.C b/gcc/testsuite/g++.dg/coroutines/pr95616-6.C
new file mode 100644
index 00000000000..e7481711c5e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr95616-6.C
@@ -0,0 +1,51 @@
+//  { dg-additional-options "-fsyntax-only" }
+
+#if __has_include(<coroutine>)
+#include <coroutine>
+#else
+#include <experimental/coroutine>
+namespace std {
+  using namespace std::experimental;
+}
+#endif
+
+class promise;
+
+struct finalSuspendAwaiter {
+  int x;
+  finalSuspendAwaiter () : x(0) { }
+  finalSuspendAwaiter (int _x) : x(_x) { }
+  ~finalSuspendAwaiter() noexcept(false) { }
+  // { dg-error {the expression 'finalSuspendAwaiter::~finalSuspendAwaiter' is required to be non-throwing} "" { target *-*-* } .-1 }
+  bool await_ready() const noexcept(true) { return false; }
+  void await_suspend(std::coroutine_handle<>) const noexcept(true) { }
+  int await_resume() const noexcept(true) { return x; }
+};
+
+struct finalSuspendObj {
+  int x;
+  finalSuspendObj () : x(0) { }
+  finalSuspendObj (int _x) : x(_x) { }
+  ~finalSuspendObj () noexcept(true) {} 
+
+  finalSuspendAwaiter operator co_await() const & noexcept(true) { 
+    return {x};
+  }
+};
+
+struct task {
+  struct promise_type {
+  task get_return_object() noexcept { return {}; }
+  std::suspend_never initial_suspend() noexcept { return {}; }
+  
+  finalSuspendObj final_suspend() noexcept { return {3}; } 
+
+  void return_void() noexcept {}
+  void unhandled_exception() noexcept {}
+  };
+};
+
+// This should be ill-formed since final_suspend() is potentially throwing.
+task f() {  
+  co_return;
+}
-- 
2.24.1


[-- Attachment #3: Type: text/plain, Size: 2 bytes --]




^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2021-03-05 17:03 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-04 19:54 [PATCH] coroutines : Do not accept throwing final await expressions [PR95616] Iain Sandoe
2021-03-04 20:05 ` Nathan Sidwell
2021-03-05 17:03   ` Iain Sandoe

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).