diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10523,7 +10523,13 @@ def note_await_ready_no_bool_conversion : Note< "return type of 'await_ready' is required to be contextually convertible to 'bool'" >; -} +def err_coroutine_promise_final_suspend_requires_nothrow : Error< + "the expression 'co_await __promise.final_suspend()' is required to be non-throwing" +>; +def note_coroutine_function_declare_noexcept : Note< + "must be declared with 'noexcept'" +>; +} // end of coroutines issue category let CategoryName = "Documentation Issue" in { def warn_not_a_doxygen_trailing_member_comment : Warning< diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1697,6 +1697,10 @@ static QualType GetTypeFromParser(ParsedType Ty, TypeSourceInfo **TInfo = nullptr); CanThrowResult canThrow(const Stmt *E); + /// Determine whether the callee of a particular function call can throw. + /// E, D and Loc are all optional. + static CanThrowResult canCalleeThrow(Sema &S, const Expr *E, const Decl *D, + SourceLocation Loc = SourceLocation()); const FunctionProtoType *ResolveExceptionSpec(SourceLocation Loc, const FunctionProtoType *FPT); void UpdateExceptionSpec(FunctionDecl *FD, diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp --- a/clang/lib/Sema/SemaCoroutine.cpp +++ b/clang/lib/Sema/SemaCoroutine.cpp @@ -24,6 +24,7 @@ #include "clang/Sema/Overload.h" #include "clang/Sema/ScopeInfo.h" #include "clang/Sema/SemaInternal.h" +#include "llvm/ADT/SmallSet.h" using namespace clang; using namespace sema; @@ -604,6 +605,72 @@ return ScopeInfo; } +/// Recursively check \p E and all its children to see if any call target +/// (including constructor call) is declared noexcept. Also any value returned +/// from the call has a noexcept destructor. +static void checkNoThrow(Sema &S, const Stmt *E, + llvm::SmallPtrSetImpl &ThrowingDecls) { + auto checkDeclNoexcept = [&](const Decl *D, bool IsDtor = false) { + // In the case of dtor, the call to dtor is implicit and hence we should + // pass nullptr to canCalleeThrow. + if (Sema::canCalleeThrow(S, IsDtor ? nullptr : cast(E), D)) { + if (ThrowingDecls.empty()) { + // First time seeing an error, emit the error message. + S.Diag(cast(S.CurContext)->getLocation(), + diag::err_coroutine_promise_final_suspend_requires_nothrow); + } + ThrowingDecls.insert(D); + } + }; + auto SC = E->getStmtClass(); + if (SC == Expr::CXXConstructExprClass) { + auto const *Ctor = cast(E)->getConstructor(); + checkDeclNoexcept(Ctor); + // Check the corresponding destructor of the constructor. + checkDeclNoexcept(Ctor->getParent()->getDestructor(), true); + } else if (SC == Expr::CallExprClass || SC == Expr::CXXMemberCallExprClass || + SC == Expr::CXXOperatorCallExprClass) { + if (!cast(E)->isTypeDependent()) { + // FIXME: Handle dependent types. + checkDeclNoexcept(cast(E)->getCalleeDecl()); + auto ReturnType = cast(E)->getCallReturnType(S.getASTContext()); + // Check the destructor of the call return type, if any. + if (ReturnType.isDestructedType() == + QualType::DestructionKind::DK_cxx_destructor) { + const auto *T = + cast(ReturnType.getCanonicalType().getTypePtr()); + checkDeclNoexcept( + dyn_cast(T->getDecl())->getDestructor(), true); + } + } + } + for (const auto *Child : E->children()) { + if (!Child) + continue; + checkNoThrow(S, Child, ThrowingDecls); + } +} + +/// Check that the expression co_await promise.final_suspend() shall not be +/// potentially-throwing. +static bool checkNoThrow(Sema &S, const Stmt *FinalSuspend) { + llvm::SmallPtrSet ThrowingDecls; + // We first collect all declarations that should not throw but not declared + // with noexcept. We then sort them based on the location before printing. + // This is to avoid emitting the same note multiple times on the same + // declaration, and also provide a deterministic order for the messages. + checkNoThrow(S, FinalSuspend, ThrowingDecls); + auto SortedDecls = llvm::SmallVector{ThrowingDecls.begin(), + ThrowingDecls.end()}; + sort(SortedDecls, [](const Decl *A, const Decl *B) { + return A->getEndLoc() < B->getEndLoc(); + }); + for (const auto *D : SortedDecls) { + S.Diag(D->getEndLoc(), diag::note_coroutine_function_declare_noexcept); + } + return ThrowingDecls.empty(); +} + bool Sema::ActOnCoroutineBodyStart(Scope *SC, SourceLocation KWLoc, StringRef Keyword) { if (!checkCoroutineContext(*this, KWLoc, Keyword)) @@ -646,7 +713,7 @@ return true; StmtResult FinalSuspend = buildSuspends("final_suspend"); - if (FinalSuspend.isInvalid()) + if (FinalSuspend.isInvalid() || !checkNoThrow(*this, FinalSuspend.get())) return true; ScopeInfo->setCoroutineSuspends(InitSuspend.get(), FinalSuspend.get()); diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -999,10 +999,8 @@ return R; } -/// Determine whether the callee of a particular function call can throw. -/// E and D are both optional, but at least one of E and Loc must be specified. -static CanThrowResult canCalleeThrow(Sema &S, const Expr *E, const Decl *D, - SourceLocation Loc = SourceLocation()) { +CanThrowResult Sema::canCalleeThrow(Sema &S, const Expr *E, const Decl *D, + SourceLocation Loc) { // As an extension, we assume that __attribute__((nothrow)) functions don't // throw. if (D && isa(D) && D->hasAttr()) @@ -1048,7 +1046,8 @@ if (!FT) return CT_Can; - FT = S.ResolveExceptionSpec(Loc.isInvalid() ? E->getBeginLoc() : Loc, FT); + if (Loc.isValid() || (Loc.isInvalid() && E)) + FT = S.ResolveExceptionSpec(Loc.isInvalid() ? E->getBeginLoc() : Loc, FT); if (!FT) return CT_Can; @@ -1069,7 +1068,7 @@ VD->getType()->getBaseElementTypeUnsafe()->getAsCXXRecordDecl()) { if (auto *Dtor = RD->getDestructor()) { CT = mergeCanThrow( - CT, canCalleeThrow(Self, nullptr, Dtor, VD->getLocation())); + CT, Sema::canCalleeThrow(Self, nullptr, Dtor, VD->getLocation())); } } } diff --git a/clang/test/AST/Inputs/std-coroutine.h b/clang/test/AST/Inputs/std-coroutine.h --- a/clang/test/AST/Inputs/std-coroutine.h +++ b/clang/test/AST/Inputs/std-coroutine.h @@ -10,12 +10,12 @@ template struct coroutine_handle { - static coroutine_handle from_address(void *); + static coroutine_handle from_address(void *) noexcept; }; template <> struct coroutine_handle { template - coroutine_handle(coroutine_handle); + coroutine_handle(coroutine_handle) noexcept; static coroutine_handle from_address(void *); }; @@ -26,9 +26,9 @@ }; struct suspend_never { - bool await_ready() { return true; } - void await_suspend(coroutine_handle<>) {} - void await_resume() {} + bool await_ready() noexcept { return true; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} }; } // namespace experimental diff --git a/clang/test/AST/coroutine-source-location-crash.cpp b/clang/test/AST/coroutine-source-location-crash.cpp --- a/clang/test/AST/coroutine-source-location-crash.cpp +++ b/clang/test/AST/coroutine-source-location-crash.cpp @@ -24,7 +24,7 @@ struct promise_type { coro_t get_return_object(); suspend_never initial_suspend(); - suspend_never final_suspend(); + suspend_never final_suspend() noexcept; void return_void(); static void unhandled_exception(); }; diff --git a/clang/test/Analysis/more-dtors-cfg-output.cpp b/clang/test/Analysis/more-dtors-cfg-output.cpp --- a/clang/test/Analysis/more-dtors-cfg-output.cpp +++ b/clang/test/Analysis/more-dtors-cfg-output.cpp @@ -278,16 +278,16 @@ namespace std::experimental { template struct coroutine_handle { - static coroutine_handle from_address(void *); + static coroutine_handle from_address(void *) noexcept; }; } struct TestPromise { TestPromise initial_suspend(); - TestPromise final_suspend(); - bool await_ready(); - void await_suspend(const std::experimental::coroutine_handle &); - void await_resume(); + TestPromise final_suspend() noexcept; + bool await_ready() noexcept; + void await_suspend(const std::experimental::coroutine_handle &) noexcept; + void await_resume() noexcept; Foo return_value(const Bar &); Bar get_return_object(); void unhandled_exception(); diff --git a/clang/test/CodeGenCXX/ubsan-coroutines.cpp b/clang/test/CodeGenCXX/ubsan-coroutines.cpp --- a/clang/test/CodeGenCXX/ubsan-coroutines.cpp +++ b/clang/test/CodeGenCXX/ubsan-coroutines.cpp @@ -32,7 +32,7 @@ struct promise_type { task get_return_object() { return task(); } suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} }; diff --git a/clang/test/CodeGenCoroutines/Inputs/coroutine.h b/clang/test/CodeGenCoroutines/Inputs/coroutine.h --- a/clang/test/CodeGenCoroutines/Inputs/coroutine.h +++ b/clang/test/CodeGenCoroutines/Inputs/coroutine.h @@ -72,9 +72,9 @@ void await_resume() {} }; struct suspend_never { - bool await_ready() { return true; } - void await_suspend(coroutine_handle<>) {} - void await_resume() {} + bool await_ready() noexcept { return true; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} }; }}} diff --git a/clang/test/CodeGenCoroutines/coro-alloc.cpp b/clang/test/CodeGenCoroutines/coro-alloc.cpp --- a/clang/test/CodeGenCoroutines/coro-alloc.cpp +++ b/clang/test/CodeGenCoroutines/coro-alloc.cpp @@ -10,7 +10,7 @@ template struct coroutine_handle { coroutine_handle() = default; - static coroutine_handle from_address(void *) { return {}; } + static coroutine_handle from_address(void *) noexcept { return {}; } }; template <> @@ -18,7 +18,7 @@ static coroutine_handle from_address(void *) { return {}; } coroutine_handle() = default; template - coroutine_handle(coroutine_handle) {} + coroutine_handle(coroutine_handle) noexcept {} }; } // end namespace experimental @@ -36,9 +36,9 @@ struct suspend_always { - bool await_ready() { return false; } - void await_suspend(std::experimental::coroutine_handle<>) {} - void await_resume() {} + bool await_ready() noexcept { return false; } + void await_suspend(std::experimental::coroutine_handle<>) noexcept {} + void await_resume() noexcept {} }; struct global_new_delete_tag {}; @@ -48,7 +48,7 @@ struct promise_type { void get_return_object() {} suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} }; }; @@ -89,7 +89,7 @@ void *operator new(unsigned long); void get_return_object() {} suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} }; }; @@ -115,7 +115,7 @@ int, float, double); void get_return_object() {} suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} }; }; @@ -145,7 +145,7 @@ struct promise_type { void get_return_object() {} suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} }; }; @@ -168,7 +168,7 @@ void operator delete(void*); void get_return_object() {} suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} }; }; @@ -193,7 +193,7 @@ void operator delete(void*, unsigned long); void get_return_object() {} suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} }; }; @@ -218,7 +218,7 @@ struct promise_type { int get_return_object() { return 0; } suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} static int get_return_object_on_allocation_failure() { return -1; } }; diff --git a/clang/test/CodeGenCoroutines/coro-always-inline.cpp b/clang/test/CodeGenCoroutines/coro-always-inline.cpp --- a/clang/test/CodeGenCoroutines/coro-always-inline.cpp +++ b/clang/test/CodeGenCoroutines/coro-always-inline.cpp @@ -14,22 +14,22 @@ struct handle {}; struct awaitable { - bool await_ready() { return true; } + bool await_ready() noexcept { return true; } // CHECK-NOT: await_suspend - inline void __attribute__((__always_inline__)) await_suspend(handle) {} - bool await_resume() { return true; } + inline void __attribute__((__always_inline__)) await_suspend(handle) noexcept {} + bool await_resume() noexcept { return true; } }; template struct coroutine_handle { - static handle from_address(void *address) { return {}; } + static handle from_address(void *address) noexcept { return {}; } }; template struct coroutine_traits { struct promise_type { awaitable initial_suspend() { return {}; } - awaitable final_suspend() { return {}; } + awaitable final_suspend() noexcept { return {}; } void return_void() {} T get_return_object() { return T(); } void unhandled_exception() {} diff --git a/clang/test/CodeGenCoroutines/coro-await-domination.cpp b/clang/test/CodeGenCoroutines/coro-await-domination.cpp --- a/clang/test/CodeGenCoroutines/coro-await-domination.cpp +++ b/clang/test/CodeGenCoroutines/coro-await-domination.cpp @@ -7,7 +7,7 @@ struct promise_type { coro get_return_object(); suspend_never initial_suspend(); - suspend_never final_suspend(); + suspend_never final_suspend() noexcept; void return_void(); static void unhandled_exception(); }; @@ -35,4 +35,3 @@ x = co_await A{}; consume(x); } - diff --git a/clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp b/clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp --- a/clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp +++ b/clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp @@ -22,7 +22,7 @@ struct promise_type { auto get_return_object() { return throwing_task{}; } auto initial_suspend() { return throwing_awaitable{}; } - auto final_suspend() { return coro::suspend_never{}; } + auto final_suspend() noexcept { return coro::suspend_never{}; } void return_void() {} void unhandled_exception() {} }; @@ -76,7 +76,7 @@ // CHECK-NEXT: br label %[[COROFINAL]] // CHECK: [[COROFINAL]]: - // CHECK-NEXT: invoke void @_ZN13throwing_task12promise_type13final_suspendEv + // CHECK-NEXT: call void @_ZN13throwing_task12promise_type13final_suspendEv co_return; } @@ -90,7 +90,7 @@ struct promise_type { auto get_return_object() { return noexcept_task{}; } auto initial_suspend() { return noexcept_awaitable{}; } - auto final_suspend() { return coro::suspend_never{}; } + auto final_suspend() noexcept { return coro::suspend_never{}; } void return_void() {} void unhandled_exception() {} }; diff --git a/clang/test/CodeGenCoroutines/coro-await.cpp b/clang/test/CodeGenCoroutines/coro-await.cpp --- a/clang/test/CodeGenCoroutines/coro-await.cpp +++ b/clang/test/CodeGenCoroutines/coro-await.cpp @@ -17,7 +17,7 @@ template struct coroutine_handle : coroutine_handle<> { - static coroutine_handle from_address(void *); + static coroutine_handle from_address(void *) noexcept; }; } @@ -29,9 +29,9 @@ void await_resume(); }; struct final_susp { - bool await_ready(); - void await_suspend(std::experimental::coroutine_handle<>); - void await_resume(); + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; }; struct suspend_always { @@ -46,7 +46,7 @@ struct promise_type { void get_return_object(); init_susp initial_suspend(); - final_susp final_suspend(); + final_susp final_suspend() noexcept; void return_void(); }; }; @@ -119,7 +119,7 @@ struct promise_type { void get_return_object(); init_susp initial_suspend(); - final_susp final_suspend(); + final_susp final_suspend() noexcept; void return_void(); suspend_maybe yield_value(int); }; @@ -295,7 +295,7 @@ struct promise_type { void get_return_object(); init_susp initial_suspend(); - final_susp final_suspend(); + final_susp final_suspend() noexcept; void return_void(); AwaitResumeReturnsLValue yield_value(int); }; diff --git a/clang/test/CodeGenCoroutines/coro-dest-slot.cpp b/clang/test/CodeGenCoroutines/coro-dest-slot.cpp --- a/clang/test/CodeGenCoroutines/coro-dest-slot.cpp +++ b/clang/test/CodeGenCoroutines/coro-dest-slot.cpp @@ -8,7 +8,7 @@ struct promise_type { coro get_return_object(); suspend_always initial_suspend(); - suspend_never final_suspend(); + suspend_never final_suspend() noexcept; void return_void(); static void unhandled_exception(); }; diff --git a/clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp b/clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp --- a/clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp +++ b/clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp @@ -21,7 +21,7 @@ struct promise_type { RetObject get_return_object(); suspend_always initial_suspend(); - suspend_never final_suspend(); + suspend_never final_suspend() noexcept; void return_void(); static void unhandled_exception(); }; @@ -52,7 +52,7 @@ static RetObject get_return_object_on_allocation_failure(); RetObject get_return_object(); suspend_always initial_suspend(); - suspend_never final_suspend(); + suspend_never final_suspend() noexcept; void return_void(); static void unhandled_exception(); }; diff --git a/clang/test/CodeGenCoroutines/coro-newpm-pipeline.cpp b/clang/test/CodeGenCoroutines/coro-newpm-pipeline.cpp --- a/clang/test/CodeGenCoroutines/coro-newpm-pipeline.cpp +++ b/clang/test/CodeGenCoroutines/coro-newpm-pipeline.cpp @@ -33,19 +33,19 @@ struct handle {}; struct awaitable { - bool await_ready() { return true; } - void await_suspend(handle) {} - bool await_resume() { return true; } + bool await_ready() noexcept { return true; } + void await_suspend(handle) noexcept {} + bool await_resume() noexcept { return true; } }; template struct coroutine_handle { - static handle from_address(void *address) { return {}; } + static handle from_address(void *address) noexcept { return {}; } }; template struct coroutine_traits { struct promise_type { awaitable initial_suspend() { return {}; } - awaitable final_suspend() { return {}; } + awaitable final_suspend() noexcept { return {}; } void return_void() {} T get_return_object() { return T(); } void unhandled_exception() {} diff --git a/clang/test/CodeGenCoroutines/coro-params.cpp b/clang/test/CodeGenCoroutines/coro-params.cpp --- a/clang/test/CodeGenCoroutines/coro-params.cpp +++ b/clang/test/CodeGenCoroutines/coro-params.cpp @@ -142,7 +142,7 @@ promise_type() = delete; void get_return_object() {} suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} }; @@ -166,7 +166,7 @@ promise_type(some_class&, float); method get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); }; diff --git a/clang/test/CodeGenCoroutines/coro-promise-dtor.cpp b/clang/test/CodeGenCoroutines/coro-promise-dtor.cpp --- a/clang/test/CodeGenCoroutines/coro-promise-dtor.cpp +++ b/clang/test/CodeGenCoroutines/coro-promise-dtor.cpp @@ -11,7 +11,7 @@ struct promise_type { coro_t get_return_object(); coro::suspend_never initial_suspend(); - coro::suspend_never final_suspend(); + coro::suspend_never final_suspend() noexcept; void return_void(); promise_type(); ~promise_type(); diff --git a/clang/test/CodeGenCoroutines/coro-ret-void.cpp b/clang/test/CodeGenCoroutines/coro-ret-void.cpp --- a/clang/test/CodeGenCoroutines/coro-ret-void.cpp +++ b/clang/test/CodeGenCoroutines/coro-ret-void.cpp @@ -8,7 +8,7 @@ struct promise_type { coro1 get_return_object(); coro::suspend_never initial_suspend(); - coro::suspend_never final_suspend(); + coro::suspend_never final_suspend() noexcept; void return_void(); }; }; @@ -39,7 +39,7 @@ struct promise_type { coro2 get_return_object(); coro::suspend_never initial_suspend(); - coro::suspend_never final_suspend(); + coro::suspend_never final_suspend() noexcept; void return_value(int); }; }; diff --git a/clang/test/CodeGenCoroutines/coro-return-voidtype-initlist.cpp b/clang/test/CodeGenCoroutines/coro-return-voidtype-initlist.cpp --- a/clang/test/CodeGenCoroutines/coro-return-voidtype-initlist.cpp +++ b/clang/test/CodeGenCoroutines/coro-return-voidtype-initlist.cpp @@ -14,7 +14,7 @@ struct coroutine_handle<> {}; template struct coroutine_handle : coroutine_handle<> { - static coroutine_handle from_address(void *); + static coroutine_handle from_address(void *) noexcept; }; struct e { int await_ready(); @@ -29,13 +29,13 @@ struct f; struct g { struct h { - int await_ready(); + int await_ready() noexcept; template - void await_suspend(std::experimental::coroutine_handle); - void await_resume(); + void await_suspend(std::experimental::coroutine_handle) noexcept; + void await_resume() noexcept; }; std::experimental::e initial_suspend(); - h final_suspend(); + h final_suspend() noexcept; template auto await_transform(ag) { return ah(ag()); } }; diff --git a/clang/test/CodeGenCoroutines/coro-return.cpp b/clang/test/CodeGenCoroutines/coro-return.cpp --- a/clang/test/CodeGenCoroutines/coro-return.cpp +++ b/clang/test/CodeGenCoroutines/coro-return.cpp @@ -5,27 +5,27 @@ template struct coroutine_handle { coroutine_handle() = default; - static coroutine_handle from_address(void *) { return {}; } + static coroutine_handle from_address(void *) noexcept { return {}; } }; template <> struct coroutine_handle { static coroutine_handle from_address(void *) { return {}; } coroutine_handle() = default; template - coroutine_handle(coroutine_handle) {} + coroutine_handle(coroutine_handle) noexcept {} }; } struct suspend_always { - bool await_ready(); - void await_suspend(std::experimental::coroutine_handle<>); - void await_resume(); + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; }; template <> struct std::experimental::coroutine_traits { struct promise_type { void get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); }; }; @@ -44,7 +44,7 @@ struct promise_type { int get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_value(int); }; }; diff --git a/clang/test/CodeGenCoroutines/coro-unhandled-exception.cpp b/clang/test/CodeGenCoroutines/coro-unhandled-exception.cpp --- a/clang/test/CodeGenCoroutines/coro-unhandled-exception.cpp +++ b/clang/test/CodeGenCoroutines/coro-unhandled-exception.cpp @@ -17,7 +17,7 @@ return {}; } coro::suspend_never initial_suspend() { return {}; } - coro::suspend_never final_suspend() { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } void return_void(){} void unhandled_exception() noexcept; }; @@ -48,11 +48,9 @@ // CHECK: [[CATCHRETDEST]]: // CHECK-NEXT: br label %[[TRYCONT:.+]] // CHECK: [[TRYCONT]]: -// CHECK-NEXT: br label %[[RESUMECONT:.+]] -// CHECK: [[RESUMECONT]]: // CHECK-NEXT: br label %[[COROFIN:.+]] // CHECK: [[COROFIN]]: -// CHECK-NEXT: invoke void @"?final_suspend@promise_type@coro_t@@QEAA?AUsuspend_never@coroutines_v1@experimental@std@@XZ"( +// CHECK-NEXT: call void @"?final_suspend@promise_type@coro_t@@QEAA?AUsuspend_never@coroutines_v1@experimental@std@@XZ"( // CHECK-LPAD: @_Z1fv( // CHECK-LPAD: invoke void @_Z9may_throwv() @@ -69,8 +67,6 @@ // CHECK-LPAD: [[CATCHRETDEST]]: // CHECK-LPAD-NEXT: br label %[[TRYCONT:.+]] // CHECK-LPAD: [[TRYCONT]]: -// CHECK-LPAD: br label %[[RESUMECONT:.+]] -// CHECK-LPAD: [[RESUMECONT]]: -// CHECK-LPAD-NEXT: br label %[[COROFIN:.+]] +// CHECK-LPAD: br label %[[COROFIN:.+]] // CHECK-LPAD: [[COROFIN]]: -// CHECK-LPAD-NEXT: invoke void @_ZN6coro_t12promise_type13final_suspendEv( +// CHECK-LPAD-NEXT: call void @_ZN6coro_t12promise_type13final_suspendEv( diff --git a/clang/test/Index/coroutines.cpp b/clang/test/Index/coroutines.cpp --- a/clang/test/Index/coroutines.cpp +++ b/clang/test/Index/coroutines.cpp @@ -7,7 +7,7 @@ struct promise_void { void get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); }; diff --git a/clang/test/SemaCXX/Inputs/std-coroutine.h b/clang/test/SemaCXX/Inputs/std-coroutine.h --- a/clang/test/SemaCXX/Inputs/std-coroutine.h +++ b/clang/test/SemaCXX/Inputs/std-coroutine.h @@ -10,25 +10,25 @@ template struct coroutine_handle { - static coroutine_handle from_address(void *); + static coroutine_handle from_address(void *) noexcept; }; template <> struct coroutine_handle { template - coroutine_handle(coroutine_handle); + coroutine_handle(coroutine_handle) noexcept; static coroutine_handle from_address(void *); }; struct suspend_always { - bool await_ready() { return false; } - void await_suspend(coroutine_handle<>) {} - void await_resume() {} + bool await_ready() noexcept { return false; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} }; struct suspend_never { - bool await_ready() { return true; } - void await_suspend(coroutine_handle<>) {} - void await_resume() {} + bool await_ready() noexcept { return true; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} }; } // namespace experimental diff --git a/clang/test/SemaCXX/co_await-range-for.cpp b/clang/test/SemaCXX/co_await-range-for.cpp --- a/clang/test/SemaCXX/co_await-range-for.cpp +++ b/clang/test/SemaCXX/co_await-range-for.cpp @@ -44,7 +44,7 @@ void return_void(); void unhandled_exception(); suspend_never initial_suspend(); - suspend_never final_suspend(); + suspend_never final_suspend() noexcept; template Awaiter await_transform(T *) = delete; // expected-note {{explicitly deleted}} }; @@ -62,7 +62,7 @@ void return_void(); void unhandled_exception(); suspend_never initial_suspend(); - suspend_never final_suspend(); + suspend_never final_suspend() noexcept; template Awaiter await_transform(BeginTag) = delete; // expected-note 1+ {{explicitly deleted}} @@ -96,7 +96,7 @@ void return_void(); void unhandled_exception(); suspend_never initial_suspend(); - suspend_never final_suspend(); + suspend_never final_suspend() noexcept; template Awaiter await_transform(BeginTag e); @@ -137,7 +137,7 @@ void return_void(); void unhandled_exception(); suspend_never initial_suspend(); - suspend_never final_suspend(); + suspend_never final_suspend() noexcept; template CoawaitTag await_transform(BeginTag e); template diff --git a/clang/test/SemaCXX/coreturn-eh.cpp b/clang/test/SemaCXX/coreturn-eh.cpp --- a/clang/test/SemaCXX/coreturn-eh.cpp +++ b/clang/test/SemaCXX/coreturn-eh.cpp @@ -17,7 +17,7 @@ struct promise_void_return_value { void get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); void return_value(object); }; @@ -26,7 +26,7 @@ struct promise_type { VoidTagReturnValue get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); void return_value(object); }; diff --git a/clang/test/SemaCXX/coreturn.cpp b/clang/test/SemaCXX/coreturn.cpp --- a/clang/test/SemaCXX/coreturn.cpp +++ b/clang/test/SemaCXX/coreturn.cpp @@ -13,7 +13,7 @@ struct promise_void { void get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); }; @@ -21,7 +21,7 @@ struct promise_void_return_value { void get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); void return_value(int); }; @@ -30,7 +30,7 @@ struct promise_type { VoidTagNoReturn get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); }; }; @@ -39,7 +39,7 @@ struct promise_type { VoidTagReturnValue get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); void return_value(int); }; @@ -49,7 +49,7 @@ struct promise_type { VoidTagReturnVoid get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); void return_void(); }; @@ -58,7 +58,7 @@ struct promise_float { float get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); }; @@ -66,7 +66,7 @@ struct promise_int { int get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_value(int); void unhandled_exception(); }; diff --git a/clang/test/SemaCXX/coroutine-final-suspend-noexcept.cpp b/clang/test/SemaCXX/coroutine-final-suspend-noexcept.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/coroutine-final-suspend-noexcept.cpp @@ -0,0 +1,62 @@ +// This file contains references to sections of the Coroutines TS, which can be +// found at http://wg21.link/coroutines. + +// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -verify %s -fcxx-exceptions -fexceptions -Wunused-result + +namespace std { +namespace experimental { + +template +struct coroutine_traits { using promise_type = typename Ret::promise_type; }; + +template +struct coroutine_handle { + static coroutine_handle from_address(void *); // expected-note {{must be declared with 'noexcept'}} +}; +template <> +struct coroutine_handle { + template + coroutine_handle(coroutine_handle); // expected-note {{must be declared with 'noexcept'}} +}; + +struct suspend_never { + bool await_ready() { return true; } // expected-note {{must be declared with 'noexcept'}} + void await_suspend(coroutine_handle<>) {} // expected-note {{must be declared with 'noexcept'}} + void await_resume() {} // expected-note {{must be declared with 'noexcept'}} + ~suspend_never() noexcept(false); // expected-note {{must be declared with 'noexcept'}} +}; + +struct suspend_always { + bool await_ready() { return false; } + void await_suspend(coroutine_handle<>) {} + void await_resume() {} + suspend_never operator co_await(); // expected-note {{must be declared with 'noexcept'}} + ~suspend_always() noexcept(false); // expected-note {{must be declared with 'noexcept'}} +}; + +} // namespace experimental +} // namespace std + +using namespace std::experimental; + +struct A { + bool await_ready(); + void await_resume(); + template + void await_suspend(F); +}; + +struct coro_t { + struct promise_type { + coro_t get_return_object(); + suspend_never initial_suspend(); + suspend_always final_suspend(); // expected-note {{must be declared with 'noexcept'}} + void return_void(); + static void unhandled_exception(); + }; +}; + +coro_t f(int n) { // expected-error {{the expression 'co_await __promise.final_suspend()' is required to be non-throwing}} + A a{}; + co_await a; +} diff --git a/clang/test/SemaCXX/coroutine-rvo.cpp b/clang/test/SemaCXX/coroutine-rvo.cpp --- a/clang/test/SemaCXX/coroutine-rvo.cpp +++ b/clang/test/SemaCXX/coroutine-rvo.cpp @@ -49,7 +49,7 @@ struct task { struct promise_type { auto initial_suspend() { return suspend_never{}; } - auto final_suspend() { return suspend_never{}; } + auto final_suspend() noexcept { return suspend_never{}; } auto get_return_object() { return task{}; } static void unhandled_exception() {} void return_value(T&& value) {} diff --git a/clang/test/SemaCXX/coroutine-unhandled_exception-warning.cpp b/clang/test/SemaCXX/coroutine-unhandled_exception-warning.cpp --- a/clang/test/SemaCXX/coroutine-unhandled_exception-warning.cpp +++ b/clang/test/SemaCXX/coroutine-unhandled_exception-warning.cpp @@ -23,7 +23,7 @@ #endif void get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); }; diff --git a/clang/test/SemaCXX/coroutine-uninitialized-warning-crash.cpp b/clang/test/SemaCXX/coroutine-uninitialized-warning-crash.cpp --- a/clang/test/SemaCXX/coroutine-uninitialized-warning-crash.cpp +++ b/clang/test/SemaCXX/coroutine-uninitialized-warning-crash.cpp @@ -16,7 +16,7 @@ struct promise_type { coro_t get_return_object() { return {}; } suspend_never initial_suspend() { return {}; } - suspend_never final_suspend() { return {}; } + suspend_never final_suspend() noexcept { return {}; } A yield_value(int) { return {}; } void return_void() {} static void unhandled_exception() {} diff --git a/clang/test/SemaCXX/coroutines.cpp b/clang/test/SemaCXX/coroutines.cpp --- a/clang/test/SemaCXX/coroutines.cpp +++ b/clang/test/SemaCXX/coroutines.cpp @@ -52,21 +52,24 @@ }; struct awaitable { - bool await_ready(); - template void await_suspend(F); - void await_resume(); + bool await_ready() noexcept; + template + void await_suspend(F) noexcept; + void await_resume() noexcept; } a; struct suspend_always { - bool await_ready() { return false; } - template void await_suspend(F); - void await_resume() {} + bool await_ready() noexcept { return false; } + template + void await_suspend(F) noexcept; + void await_resume() noexcept {} }; struct suspend_never { - bool await_ready() { return true; } - template void await_suspend(F); - void await_resume() {} + bool await_ready() noexcept { return true; } + template + void await_suspend(F) noexcept; + void await_resume() noexcept {} }; struct auto_await_suspend { @@ -127,7 +130,7 @@ struct promise { void get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; awaitable yield_value(int); // expected-note 2{{candidate}} awaitable yield_value(yielded_thing); // expected-note 2{{candidate}} not_awaitable yield_value(void()); // expected-note 2{{candidate}} @@ -138,7 +141,7 @@ struct promise_void { void get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); }; @@ -152,13 +155,13 @@ namespace experimental { template struct coroutine_handle { - static coroutine_handle from_address(void *); + static coroutine_handle from_address(void *) noexcept; }; template <> struct coroutine_handle { template - coroutine_handle(coroutine_handle); - static coroutine_handle from_address(void *); + coroutine_handle(coroutine_handle) noexcept; + static coroutine_handle from_address(void *) noexcept; }; }} // namespace std::experimental @@ -402,7 +405,7 @@ namespace adl_ns { struct coawait_arg_type {}; -awaitable operator co_await(coawait_arg_type); +awaitable operator co_await(coawait_arg_type) noexcept; } namespace dependent_operator_co_await_lookup { @@ -434,7 +437,7 @@ typedef transform_awaitable await_arg; coro get_return_object(); transformed initial_suspend(); - ::adl_ns::coawait_arg_type final_suspend(); + ::adl_ns::coawait_arg_type final_suspend() noexcept; transformed await_transform(transform_awaitable); void unhandled_exception(); void return_void(); @@ -444,7 +447,7 @@ typedef AwaitArg await_arg; coro get_return_object(); awaitable initial_suspend(); - awaitable final_suspend(); + awaitable final_suspend() noexcept; void unhandled_exception(); void return_void(); }; @@ -529,7 +532,7 @@ void return_value(int()); suspend_never initial_suspend(); - suspend_never final_suspend(); + suspend_never final_suspend() noexcept; void get_return_object(); void unhandled_exception(); }; @@ -563,7 +566,7 @@ struct bad_promise_1 { suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); void return_void(); }; @@ -573,7 +576,7 @@ struct bad_promise_2 { coro get_return_object(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); void return_void(); }; @@ -588,14 +591,14 @@ void unhandled_exception(); void return_void(); }; -coro missing_final_suspend() { // expected-error {{no member named 'final_suspend' in 'bad_promise_3'}} +coro missing_final_suspend() noexcept { // expected-error {{no member named 'final_suspend' in 'bad_promise_3'}} co_await a; } struct bad_promise_4 { coro get_return_object(); not_awaitable initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); }; // FIXME: This diagnostic is terrible. @@ -607,7 +610,7 @@ struct bad_promise_5 { coro get_return_object(); suspend_always initial_suspend(); - not_awaitable final_suspend(); + not_awaitable final_suspend() noexcept; void return_void(); }; // FIXME: This diagnostic is terrible. @@ -619,7 +622,7 @@ struct bad_promise_6 { coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); void return_void(); // expected-note 2 {{member 'return_void' first declared here}} void return_value(int) const; // expected-note 2 {{member 'return_value' first declared here}} @@ -638,7 +641,7 @@ struct bad_promise_7 { // expected-note 2 {{defined here}} coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); }; coro no_unhandled_exception() { // expected-error {{'bad_promise_7' is required to declare the member 'unhandled_exception()'}} @@ -658,7 +661,7 @@ struct bad_promise_8 : bad_promise_base { coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception() __attribute__((unavailable)); // expected-note 2 {{marked unavailable here}} void unhandled_exception() const; void unhandled_exception(void *) const; @@ -680,7 +683,7 @@ struct bad_promise_9 { coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void await_transform(void *); awaitable await_transform(int) __attribute__((unavailable)); // expected-note {{explicitly marked unavailable}} void return_void(); @@ -693,7 +696,7 @@ struct bad_promise_10 { coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; int await_transform; void return_void(); void unhandled_exception(); @@ -712,7 +715,7 @@ struct good_promise_1 { coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); static const call_operator await_transform; using Fn = void (*)(); @@ -750,7 +753,7 @@ struct good_promise_2 { float get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); }; @@ -783,7 +786,7 @@ struct promise_type { int get_return_object() {} suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} int get_return_object_on_allocation_failure(); // expected-error{{'promise_type': 'get_return_object_on_allocation_failure()' must be a static member function}} void unhandled_exception(); @@ -797,7 +800,7 @@ struct bad_promise_11 { coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); void return_void(); @@ -820,7 +823,7 @@ struct bad_promise_12 { coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); void return_void(); static coro get_return_object_on_allocation_failure(); @@ -842,7 +845,7 @@ struct good_promise_13 { coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); void return_void(); static coro get_return_object_on_allocation_failure(); @@ -860,7 +863,7 @@ struct good_promise_custom_new_operator { coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); void *operator new(SizeT, double, float, int); @@ -876,7 +879,7 @@ struct good_promise_nonstatic_member_custom_new_operator { coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); void *operator new(SizeT, coroutine_nonstatic_member_struct &, double); @@ -886,7 +889,7 @@ static coro get_return_object_on_allocation_failure(); coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); void *operator new(SizeT, double, float, int) noexcept; @@ -903,7 +906,7 @@ struct promise_type { void get_return_object() {} //expected-note {{member 'get_return_object' declared here}} suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception(); }; @@ -920,7 +923,7 @@ struct promise_type { void *get_return_object() {} //expected-note {{member 'get_return_object' declared here}} suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception(); }; @@ -938,7 +941,7 @@ int get_return_object() {} static void get_return_object_on_allocation_failure() {} //expected-note {{member 'get_return_object_on_allocation_failure' declared here}} suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception(); }; @@ -957,7 +960,7 @@ int get_return_object() {} static char *get_return_object_on_allocation_failure() {} //expected-note {{member 'get_return_object_on_allocation_failure' declared}} suspend_always initial_suspend() { return {}; } - suspend_always final_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception(); }; @@ -971,7 +974,7 @@ struct bad_promise_no_return_func { // expected-note {{'bad_promise_no_return_func' defined here}} coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void unhandled_exception(); }; // FIXME: The PDTS currently specifies this as UB, technically forbidding a @@ -1083,7 +1086,7 @@ CoroMemberTag get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; AwaitTestT yield_value(int); @@ -1292,7 +1295,7 @@ bad_promise_deleted_constructor() = delete; coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); }; @@ -1314,7 +1317,7 @@ good_promise_default_constructor() = default; coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); }; @@ -1332,7 +1335,7 @@ good_promise_custom_constructor() = delete; coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); }; @@ -1359,7 +1362,7 @@ bad_promise_no_matching_constructor() = delete; coro get_return_object(); suspend_always initial_suspend(); - suspend_always final_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); }; @@ -1383,26 +1386,25 @@ class awaitable_no_unused_warn { public: using handle_type = std::experimental::coroutine_handle<>; - constexpr bool await_ready() { return false; } + constexpr bool await_ready() noexcept { return false; } void await_suspend(handle_type) noexcept {} - int await_resume() { return 1; } + int await_resume() noexcept { return 1; } }; class awaitable_unused_warn { public: using handle_type = std::experimental::coroutine_handle<>; - constexpr bool await_ready() { return false; } + constexpr bool await_ready() noexcept { return false; } void await_suspend(handle_type) noexcept {} - [[nodiscard]] - int await_resume() { return 1; } + [[nodiscard]] int await_resume() noexcept { return 1; } }; template struct check_warning_promise { coro get_return_object(); Await initial_suspend(); - Await final_suspend(); + Await final_suspend() noexcept; Await yield_value(int); void return_void(); void unhandled_exception();