diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 93ec15a7f0959..30f2d0f190a46 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -334,6 +334,10 @@ Attribute Changes in Clang [[clang::code_align(A)]] for(;;) { } } +- Clang now introduced ``[[clang::coro_lifetimebound]]`` attribute. + All parameters of a function are considered to be lifetime bound if the function + returns a type annotated with ``[[clang::coro_lifetimebound]]`` and ``[[clang::coro_return_type]]``. + Improvements to Clang's diagnostics ----------------------------------- - Clang constexpr evaluator now prints template arguments when displaying diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index c2fbdfc66c540..03ed6accf700c 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1110,6 +1110,14 @@ def CoroWrapper : InheritableAttr { let SimpleHandler = 1; } +def CoroLifetimeBound : InheritableAttr { + let Spellings = [Clang<"coro_lifetimebound">]; + let Subjects = SubjectList<[CXXRecord]>; + let LangOpts = [CPlusPlus]; + let Documentation = [CoroLifetimeBoundDoc]; + let SimpleHandler = 1; +} + // OSObject-based attributes. def OSConsumed : InheritableParamAttr { let Spellings = [Clang<"os_consumed">]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index b5ceb47b6b8ad..7406710a56392 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -7483,7 +7483,6 @@ generation of the other destruction cases, optimizing the above `foo.destroy` to }]; } - def CoroReturnTypeAndWrapperDoc : Documentation { let Category = DocCatDecl; let Content = [{ @@ -7581,3 +7580,60 @@ alignment boundary. Its value must be a power of 2, between 1 and 4096 }]; } + +def CoroLifetimeBoundDoc : Documentation { + let Category = DocCatDecl; + let Content = [{ +The ``[[clang::coro_lifetimebound]]`` is a class attribute which can be applied +to a `coroutine return type (CRT) ` _ (i.e. +it should also be annotated with ``[[clang::coro_return_type]]``). + +All parameters of a function are considered to be lifetime bound. See documentation +of ``[[clang::lifetimebound]]`` for more `details _`. +if the function returns a coroutine return type (CRT) annotated with ``[[clang::coro_lifetimebound]]``. + +Reference parameters of a coroutine are susceptible to capturing references to temporaries or local variables. + +For example, + +.. code-block:: c++ + + task coro(const int& a) { co_return a + 1; } + task dangling_refs(int a) { + // `coro` captures reference to a temporary. `foo` would now contain a dangling reference to `a`. + auto foo = coro(1); + // `coro` captures reference to local variable `a` which is destroyed after the return. + return coro(a); + } + +`Lifetime bound _` static analysis +can be used to detect such instances when coroutines capture references which may die earlier than the +coroutine frame itself. In the above example, if the CRT `task` is annotated with +``[[clang::coro_lifetimebound]]``, then lifetime bound analysis would detect capturing reference to +temporaries or return address of a local variable. + +Both coroutines and coroutine wrappers are part of this analysis. + +.. code-block:: c++ + + template struct [[clang::coro_return_type, clang::coro_lifetimebound]] Task { + using promise_type = some_promise_type; + }; + + Task coro(const int& a) { co_return a + 1; } + Task [[clang::coro_wrapper]] coro_wrapper(const int& a, const int& b) { + return a > b ? coro(a) : coro(b); + } + Task temporary_reference() { + auto foo = coro(1); // warning: capturing reference to a temporary which would die after the expression. + + int a = 1; + auto bar = coro_wrapper(a, 0); // warning: `b` captures reference to a temporary. + + co_return co_await coro(1); // fine. + } + [[clang::coro_wrapper]] Task stack_reference(int a) { + return coro(a); // warning: returning address of stack variable `a`. + } +}]; +} diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 80b51b09bf544..c0c321f0f200d 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -7580,10 +7580,15 @@ static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call, if (ObjectArg && implicitObjectParamIsLifetimeBound(Callee)) VisitLifetimeBoundArg(Callee, ObjectArg); + bool CheckCoroCall = false; + if (const auto *RD = Callee->getReturnType()->getAsRecordDecl()) { + CheckCoroCall = RD->hasAttr() && + RD->hasAttr(); + } for (unsigned I = 0, N = std::min(Callee->getNumParams(), Args.size()); I != N; ++I) { - if (Callee->getParamDecl(I)->hasAttr()) + if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr()) VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]); } } diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test index a57bc011c0594..dd91f4f88ad68 100644 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -56,9 +56,10 @@ // CHECK-NEXT: ConsumableAutoCast (SubjectMatchRule_record) // CHECK-NEXT: ConsumableSetOnRead (SubjectMatchRule_record) // CHECK-NEXT: Convergent (SubjectMatchRule_function) +// CHECK-NEXT: CoroLifetimeBound (SubjectMatchRule_record) // CHECK-NEXT: CoroOnlyDestroyWhenComplete (SubjectMatchRule_record) // CHECK-NEXT: CoroReturnType (SubjectMatchRule_record) -// CHECK-NEXT: CoroWrapper +// CHECK-NEXT: CoroWrapper (SubjectMatchRule_function) // CHECK-NEXT: CountedBy (SubjectMatchRule_field) // CHECK-NEXT: DLLExport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface) // CHECK-NEXT: DLLImport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface) diff --git a/clang/test/SemaCXX/coro-lifetimebound.cpp b/clang/test/SemaCXX/coro-lifetimebound.cpp new file mode 100644 index 0000000000000..d3e2d673ebb3c --- /dev/null +++ b/clang/test/SemaCXX/coro-lifetimebound.cpp @@ -0,0 +1,123 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 -fsyntax-only -verify -Wall -Wextra -Wno-error=unreachable-code -Wno-unused + +#include "Inputs/std-coroutine.h" + +using std::suspend_always; +using std::suspend_never; + +template struct [[clang::coro_lifetimebound, clang::coro_return_type]] Co { + struct promise_type { + Co get_return_object() { + return {}; + } + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + void return_value(const T &t); + + template + auto await_transform(const Co &) { + struct awaitable { + bool await_ready() noexcept { return false; } + void await_suspend(std::coroutine_handle<>) noexcept {} + U await_resume() noexcept { return {}; } + }; + return awaitable{}; + } + }; +}; + +Co foo_coro(const int& b) { + if (b > 0) + co_return 1; + co_return 2; +} + +int getInt() { return 0; } + +Co bar_coro(const int &b, int c) { + int x = co_await foo_coro(b); + int y = co_await foo_coro(1); + int z = co_await foo_coro(getInt()); + auto unsafe1 = foo_coro(1); // expected-warning {{temporary whose address is used as value of local variable}} + auto unsafe2 = foo_coro(getInt()); // expected-warning {{temporary whose address is used as value of local variable}} + auto safe1 = foo_coro(b); + auto safe2 = foo_coro(c); + co_return co_await foo_coro(co_await foo_coro(1)); +} + +[[clang::coro_wrapper]] Co plain_return_co(int b) { + return foo_coro(b); // expected-warning {{address of stack memory associated with parameter}} +} + +[[clang::coro_wrapper]] Co safe_forwarding(const int& b) { + return foo_coro(b); +} + +[[clang::coro_wrapper]] Co unsafe_wrapper(int b) { + return safe_forwarding(b); // expected-warning {{address of stack memory associated with parameter}} +} + +[[clang::coro_wrapper]] Co complex_plain_return(int b) { + return b > 0 + ? foo_coro(1) // expected-warning {{returning address of local temporary object}} + : bar_coro(0, 1); // expected-warning {{returning address of local temporary object}} +} + +#define CORO_WRAPPER \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wc++23-extensions\"") \ + [[clang::coro_wrapper]] \ + _Pragma("clang diagnostic pop") + +void lambdas() { + auto unsafe_lambda = [] CORO_WRAPPER (int b) { + return foo_coro(b); // expected-warning {{address of stack memory associated with parameter}} + }; + auto coro_lambda = [] (const int&) -> Co { + co_return 0; + }; + auto unsafe_coro_lambda = [&] (const int& b) -> Co { + int x = co_await coro_lambda(b); + auto safe = coro_lambda(b); + auto unsafe1 = coro_lambda(1); // expected-warning {{temporary whose address is used as value of local variable}} + auto unsafe2 = coro_lambda(getInt()); // expected-warning {{temporary whose address is used as value of local variable}} + auto unsafe3 = coro_lambda(co_await coro_lambda(b)); // expected-warning {{temporary whose address is used as value of local variable}} + co_return co_await safe; + }; + auto safe_lambda = [](int b) -> Co { + int x = co_await foo_coro(1); + co_return x + co_await foo_coro(b); + }; +} +// ============================================================================= +// Safe usage when parameters are value +// ============================================================================= +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); } +} + +// ============================================================================= +// Lifetime bound but not a Coroutine Return Type: No analysis. +// ============================================================================= +namespace not_a_crt { +template struct [[clang::coro_lifetimebound]] CoNoCRT { + struct promise_type { + CoNoCRT get_return_object() { + return {}; + } + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + void return_value(const T &t); + }; +}; + +CoNoCRT foo_coro(const int& a) { co_return a; } +CoNoCRT bar(int a) { + auto x = foo_coro(a); + co_return 1; +} +} // namespace not_a_crt