aboutsummaryrefslogtreecommitdiff
path: root/clang/lib/Sema/SemaCoroutine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang/lib/Sema/SemaCoroutine.cpp')
-rw-r--r--clang/lib/Sema/SemaCoroutine.cpp140
1 files changed, 114 insertions, 26 deletions
diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp
index 6dc9e342beb9..992cccac6405 100644
--- 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;
@@ -390,7 +391,13 @@ static Expr *maybeTailCall(Sema &S, QualType RetType, Expr *E,
return nullptr;
Expr *JustAddress = AddressExpr.get();
- // FIXME: Check that the type of AddressExpr is void*
+
+ // Check that the type of AddressExpr is void*
+ if (!JustAddress->getType().getTypePtr()->isVoidPointerType())
+ S.Diag(cast<CallExpr>(JustAddress)->getCalleeDecl()->getLocation(),
+ diag::warn_coroutine_handle_address_invalid_return_type)
+ << JustAddress->getType();
+
return buildBuiltinCall(S, Loc, Builtin::BI__builtin_coro_resume,
JustAddress);
}
@@ -502,8 +509,9 @@ VarDecl *Sema::buildCoroutinePromise(SourceLocation Loc) {
return nullptr;
auto *ScopeInfo = getCurFunction();
- // Build a list of arguments, based on the coroutine functions arguments,
- // that will be passed to the promise type's constructor.
+
+ // Build a list of arguments, based on the coroutine function's arguments,
+ // that if present will be passed to the promise type's constructor.
llvm::SmallVector<Expr *, 4> CtorArgExprs;
// Add implicit object parameter.
@@ -519,6 +527,7 @@ VarDecl *Sema::buildCoroutinePromise(SourceLocation Loc) {
}
}
+ // Add the coroutine function's parameters.
auto &Moves = ScopeInfo->CoroutineParameterMoves;
for (auto *PD : FD->parameters()) {
if (PD->getType()->isDependentType())
@@ -540,28 +549,33 @@ VarDecl *Sema::buildCoroutinePromise(SourceLocation Loc) {
CtorArgExprs.push_back(RefExpr.get());
}
- // Create an initialization sequence for the promise type using the
- // constructor arguments, wrapped in a parenthesized list expression.
- Expr *PLE = ParenListExpr::Create(Context, FD->getLocation(),
- CtorArgExprs, FD->getLocation());
- InitializedEntity Entity = InitializedEntity::InitializeVariable(VD);
- InitializationKind Kind = InitializationKind::CreateForInit(
- VD->getLocation(), /*DirectInit=*/true, PLE);
- InitializationSequence InitSeq(*this, Entity, Kind, CtorArgExprs,
- /*TopLevelOfInitList=*/false,
- /*TreatUnavailableAsInvalid=*/false);
-
- // Attempt to initialize the promise type with the arguments.
- // If that fails, fall back to the promise type's default constructor.
- if (InitSeq) {
- ExprResult Result = InitSeq.Perform(*this, Entity, Kind, CtorArgExprs);
- if (Result.isInvalid()) {
- VD->setInvalidDecl();
- } else if (Result.get()) {
- VD->setInit(MaybeCreateExprWithCleanups(Result.get()));
- VD->setInitStyle(VarDecl::CallInit);
- CheckCompleteVariableDeclaration(VD);
- }
+ // If we have a non-zero number of constructor arguments, try to use them.
+ // Otherwise, fall back to the promise type's default constructor.
+ if (!CtorArgExprs.empty()) {
+ // Create an initialization sequence for the promise type using the
+ // constructor arguments, wrapped in a parenthesized list expression.
+ Expr *PLE = ParenListExpr::Create(Context, FD->getLocation(),
+ CtorArgExprs, FD->getLocation());
+ InitializedEntity Entity = InitializedEntity::InitializeVariable(VD);
+ InitializationKind Kind = InitializationKind::CreateForInit(
+ VD->getLocation(), /*DirectInit=*/true, PLE);
+ InitializationSequence InitSeq(*this, Entity, Kind, CtorArgExprs,
+ /*TopLevelOfInitList=*/false,
+ /*TreatUnavailableAsInvalid=*/false);
+
+ // Attempt to initialize the promise type with the arguments.
+ // If that fails, fall back to the promise type's default constructor.
+ if (InitSeq) {
+ ExprResult Result = InitSeq.Perform(*this, Entity, Kind, CtorArgExprs);
+ if (Result.isInvalid()) {
+ VD->setInvalidDecl();
+ } else if (Result.get()) {
+ VD->setInit(MaybeCreateExprWithCleanups(Result.get()));
+ VD->setInitStyle(VarDecl::CallInit);
+ CheckCompleteVariableDeclaration(VD);
+ }
+ } else
+ ActOnUninitializedDecl(VD);
} else
ActOnUninitializedDecl(VD);
@@ -597,6 +611,80 @@ static FunctionScopeInfo *checkCoroutineContext(Sema &S, SourceLocation Loc,
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<const Decl *> &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<Expr>(E), D)) {
+ if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
+ // co_await promise.final_suspend() could end up calling
+ // __builtin_coro_resume for symmetric transfer if await_suspend()
+ // returns a handle. In that case, even __builtin_coro_resume is not
+ // declared as noexcept and may throw, it does not throw _into_ the
+ // coroutine that just suspended, but rather throws back out from
+ // whoever called coroutine_handle::resume(), hence we claim that
+ // logically it does not throw.
+ if (FD->getBuiltinID() == Builtin::BI__builtin_coro_resume)
+ return;
+ }
+ if (ThrowingDecls.empty()) {
+ // First time seeing an error, emit the error message.
+ S.Diag(cast<FunctionDecl>(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<CXXConstructExpr>(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<CallExpr>(E)->isTypeDependent()) {
+ checkDeclNoexcept(cast<CallExpr>(E)->getCalleeDecl());
+ auto ReturnType = cast<CallExpr>(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<RecordType>(ReturnType.getCanonicalType().getTypePtr());
+ checkDeclNoexcept(
+ dyn_cast<CXXRecordDecl>(T->getDecl())->getDestructor(), true);
+ }
+ }
+ }
+ for (const auto *Child : E->children()) {
+ if (!Child)
+ continue;
+ checkNoThrow(S, Child, ThrowingDecls);
+ }
+}
+
+bool Sema::checkFinalSuspendNoThrow(const Stmt *FinalSuspend) {
+ llvm::SmallPtrSet<const Decl *, 4> 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(*this, FinalSuspend, ThrowingDecls);
+ auto SortedDecls = llvm::SmallVector<const Decl *, 4>{ThrowingDecls.begin(),
+ ThrowingDecls.end()};
+ sort(SortedDecls, [](const Decl *A, const Decl *B) {
+ return A->getEndLoc() < B->getEndLoc();
+ });
+ for (const auto *D : SortedDecls) {
+ 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))
@@ -639,7 +727,7 @@ bool Sema::ActOnCoroutineBodyStart(Scope *SC, SourceLocation KWLoc,
return true;
StmtResult FinalSuspend = buildSuspends("final_suspend");
- if (FinalSuspend.isInvalid())
+ if (FinalSuspend.isInvalid() || !checkFinalSuspendNoThrow(FinalSuspend.get()))
return true;
ScopeInfo->setCoroutineSuspends(InitSuspend.get(), FinalSuspend.get());