diff options
author | Iain Sandoe <iain@sandoe.co.uk> | 2021-02-15 17:11:31 +0000 |
---|---|---|
committer | Iain Sandoe <iain@sandoe.co.uk> | 2021-03-05 16:56:19 +0000 |
commit | 9ee91079fd5879cba046e452ab5593372166b2ab (patch) | |
tree | 85fa95e658d91ee2e9f40e1953bceabf37d55039 | |
parent | fe55086547c9360b530e040a6673dae10ac77847 (diff) | |
download | gcc-9ee91079fd5879cba046e452ab5593372166b2ab.zip gcc-9ee91079fd5879cba046e452ab5593372166b2ab.tar.gz gcc-9ee91079fd5879cba046e452ab5593372166b2ab.tar.bz2 |
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.
-rw-r--r-- | gcc/cp/coroutines.cc | 85 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/coroutines/pr95616-0-no-exceptions.C | 50 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/coroutines/pr95616-0.C | 51 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/coroutines/pr95616-1-no-exceptions.C | 51 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/coroutines/pr95616-1.C | 51 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/coroutines/pr95616-2.C | 51 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/coroutines/pr95616-3-no-exceptions.C | 50 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/coroutines/pr95616-3.C | 51 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/coroutines/pr95616-4.C | 51 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/coroutines/pr95616-5.C | 51 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/coroutines/pr95616-6.C | 51 |
11 files changed, 593 insertions, 0 deletions
diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc index 3f88b07..ef50a5f 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 0000000..7ce811d --- /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 0000000..e500b6e --- /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 0000000..48981e6 --- /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 0000000..c398980 --- /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 0000000..6ad2519 --- /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 0000000..1dcd426 --- /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 0000000..7da1f6a --- /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 0000000..ef6a160 --- /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 0000000..930c1a7 --- /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 0000000..e748171 --- /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; +} |