diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 6ce422d66ae5b..0db39333b0ee3 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -11249,6 +11249,11 @@ class Sema final { VarDecl *buildCoroutinePromise(SourceLocation Loc); void CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body); + // Heuristically tells if the function is `get_return_object` member of a + // coroutine promise_type by matching the function name. + static bool CanBeGetReturnObject(const FunctionDecl *FD); + static bool CanBeGetReturnTypeOnAllocFailure(const FunctionDecl *FD); + // As a clang extension, enforces that a non-coroutine function must be marked // with [[clang::coro_wrapper]] if it returns a type marked with // [[clang::coro_return_type]]. diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp index bee80db8d166a..0e0f8f67dcd73 100644 --- a/clang/lib/Sema/SemaCoroutine.cpp +++ b/clang/lib/Sema/SemaCoroutine.cpp @@ -16,6 +16,7 @@ #include "CoroutineStmtBuilder.h" #include "clang/AST/ASTLambda.h" #include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/StmtCXX.h" #include "clang/Basic/Builtins.h" diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 5472b43aafd4f..eb28631ee6939 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -15912,13 +15912,26 @@ static void diagnoseImplicitlyRetainedSelf(Sema &S) { << FixItHint::CreateInsertion(P.first, "self->"); } +static bool methodHasName(const FunctionDecl *FD, StringRef Name) { + return isa(FD) && FD->param_empty() && + FD->getDeclName().isIdentifier() && FD->getName().equals(Name); +} + +bool Sema::CanBeGetReturnObject(const FunctionDecl *FD) { + return methodHasName(FD, "get_return_object"); +} + +bool Sema::CanBeGetReturnTypeOnAllocFailure(const FunctionDecl *FD) { + return FD->isStatic() && + methodHasName(FD, "get_return_object_on_allocation_failure"); +} + void Sema::CheckCoroutineWrapper(FunctionDecl *FD) { RecordDecl *RD = FD->getReturnType()->getAsRecordDecl(); if (!RD || !RD->getUnderlyingDecl()->hasAttr()) return; - // Allow `get_return_object()`. - if (FD->getDeclName().isIdentifier() && - FD->getName().equals("get_return_object") && FD->param_empty()) + // Allow some_promise_type::get_return_object(). + if (CanBeGetReturnObject(FD) || CanBeGetReturnTypeOnAllocFailure(FD)) return; if (!FD->hasAttr()) Diag(FD->getLocation(), diag::err_coroutine_return_type) << RD; diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 408ee5f775804..96900efa75fcd 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -12,6 +12,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" +#include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/ExprOpenMP.h" @@ -7583,15 +7584,27 @@ static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call, Path.pop_back(); }; - if (ObjectArg && implicitObjectParamIsLifetimeBound(Callee)) - VisitLifetimeBoundArg(Callee, ObjectArg); - bool CheckCoroCall = false; if (const auto *RD = Callee->getReturnType()->getAsRecordDecl()) { CheckCoroCall = RD->hasAttr() && RD->hasAttr() && !Callee->hasAttr(); } + + if (ObjectArg) { + bool CheckCoroObjArg = CheckCoroCall; + // Coroutine lambda objects with empty capture list are not lifetimebound. + if (auto *LE = dyn_cast(ObjectArg->IgnoreImplicit()); + LE && LE->captures().empty()) + CheckCoroObjArg = false; + // Allow `get_return_object()` as the object param (__promise) is not + // lifetimebound. + if (Sema::CanBeGetReturnObject(Callee)) + CheckCoroObjArg = false; + if (implicitObjectParamIsLifetimeBound(Callee) || CheckCoroObjArg) + VisitLifetimeBoundArg(Callee, ObjectArg); + } + for (unsigned I = 0, N = std::min(Callee->getNumParams(), Args.size()); I != N; ++I) { diff --git a/clang/test/SemaCXX/coro-lifetimebound.cpp b/clang/test/SemaCXX/coro-lifetimebound.cpp index 3fc7ca70a14a1..9e96a296562a0 100644 --- a/clang/test/SemaCXX/coro-lifetimebound.cpp +++ b/clang/test/SemaCXX/coro-lifetimebound.cpp @@ -64,6 +64,10 @@ Co bar_coro(const int &b, int c) { : bar_coro(0, 1); // expected-warning {{returning address of local temporary object}} } +// ============================================================================= +// Lambdas +// ============================================================================= +namespace lambdas { void lambdas() { auto unsafe_lambda = [] [[clang::coro_wrapper]] (int b) { return foo_coro(b); // expected-warning {{address of stack memory associated with parameter}} @@ -84,6 +88,51 @@ void lambdas() { co_return x + co_await foo_coro(b); }; } + +Co lambda_captures() { + int a = 1; + // Temporary lambda object dies. + auto lamb = [a](int x, const int& y) -> Co { // expected-warning {{temporary whose address is used as value of local variable 'lamb'}} + co_return x + y + a; + }(1, a); + // Object dies but it has no capture. + auto no_capture = []() -> Co { co_return 1; }(); + auto bad_no_capture = [](const int& a) -> Co { co_return a; }(1); // expected-warning {{temporary}} + // Temporary lambda object with lifetime extension under co_await. + int res = co_await [a](int x, const int& y) -> Co { + co_return x + y + a; + }(1, a); + // Lambda object on stack should be fine. + auto lamb2 = [a]() -> Co { co_return a; }; + auto on_stack = lamb2(); + auto res2 = co_await on_stack; + co_return 1; +} +} // namespace lambdas + +// ============================================================================= +// Member coroutines +// ============================================================================= +namespace member_coroutines{ +struct S { + Co member(const int& a) { co_return a; } +}; + +Co use() { + S s; + int a = 1; + auto test1 = s.member(1); // expected-warning {{temporary whose address is used as value of local variable}} + auto test2 = s.member(a); + auto test3 = S{}.member(a); // expected-warning {{temporary whose address is used as value of local variable}} + co_return 1; +} + +[[clang::coro_wrapper]] Co wrapper(const int& a) { + S s; + return s.member(a); // expected-warning {{address of stack memory}} +} +} // member_coroutines + // ============================================================================= // Safe usage when parameters are value // ============================================================================= @@ -91,7 +140,7 @@ namespace by_value { Co value_coro(int b) { co_return co_await foo_coro(b); } [[clang::coro_wrapper]] Co wrapper1(int b) { return value_coro(b); } [[clang::coro_wrapper]] Co wrapper2(const int& b) { return value_coro(b); } -} +} // namespace by_value // ============================================================================= // Lifetime bound but not a Coroutine Return Type: No analysis. @@ -122,11 +171,29 @@ CoNoCRT bar(int a) { namespace disable_lifetimebound { Co foo(int x) { co_return x; } -[[clang::coro_wrapper, clang::coro_disable_lifetimebound]] +[[clang::coro_wrapper, clang::coro_disable_lifetimebound]] Co foo_wrapper(const int& x) { return foo(x); } [[clang::coro_wrapper]] Co caller() { // The call to foo_wrapper is wrapper is safe. return foo_wrapper(1); } + +struct S{ +[[clang::coro_wrapper, clang::coro_disable_lifetimebound]] +Co member(const int& x) { return foo(x); } +}; + +Co use() { + S s; + int a = 1; + auto test1 = s.member(1); // param is not flagged. + auto test2 = S{}.member(a); // 'this' is not flagged. + co_return 1; +} + +[[clang::coro_wrapper]] Co return_stack_addr(const int& a) { + S s; + return s.member(a); // return of stack addr is not flagged. +} } // namespace disable_lifetimebound diff --git a/clang/test/SemaCXX/coro-return-type-and-wrapper.cpp b/clang/test/SemaCXX/coro-return-type-and-wrapper.cpp index ac49e03ba9d90..b08e1c9c065a0 100644 --- a/clang/test/SemaCXX/coro-return-type-and-wrapper.cpp +++ b/clang/test/SemaCXX/coro-return-type-and-wrapper.cpp @@ -5,11 +5,23 @@ using std::suspend_always; using std::suspend_never; +namespace std { + struct nothrow_t {}; + constexpr nothrow_t nothrow = {}; +} + +using SizeT = decltype(sizeof(int)); + +void* operator new(SizeT __sz, const std::nothrow_t&) noexcept; + template struct [[clang::coro_return_type]] Gen { struct promise_type { Gen get_return_object() { return {}; } + static Gen get_return_object_on_allocation_failure() { + return {}; + } suspend_always initial_suspend(); suspend_always final_suspend() noexcept; void unhandled_exception();