Skip to content

[coroutines] Introduce [[clang::coro_disable_lifetimebound]] #76818

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 5, 2024

Conversation

usx95
Copy link
Contributor

@usx95 usx95 commented Jan 3, 2024

Lifetime-bound analysis of reference parameters of coroutines and coroutine wrappers is helpful in surfacing memory bugs associated with using temporaries and stack variables in call expressions in plain return statements.

This is the default semantics of [[clang::coro_lifetimebound]]. But it should be okay to relax the requirements for a function when the reference arguments are not lifetime bound. For example:

A coroutine wrapper accepts a reference parameter but does not pass it to the underlying coroutine call.

[[clang::coro_wrapper]] Task<int> wrapper(const Request& req) {
  return req.shouldCallA() ? coroA() : coroB();
}

Or passes it the coroutine by value

Task<int> coro(std::string s) { co_return s.size(); }
[[clang::coro_wrapper]] wrapper(const std::string& s) { return coro(s); }

This patch allows functions to be annotated with [[clang::coro_disable_lifetime_bound]] to disable lifetime bound analysis for all calls to this function.


One missing piece here is a note suggesting using this annotation in cases of lifetime warnings. This would require some more tweaks in the lifetimebound analysis to recognize violations involving coroutines only and produce this note only in those cases.

@usx95 usx95 marked this pull request as ready for review January 3, 2024 14:11
@usx95 usx95 requested a review from ilya-biryukov January 3, 2024 14:11
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jan 3, 2024
@llvmbot
Copy link
Member

llvmbot commented Jan 3, 2024

@llvm/pr-subscribers-coroutines

@llvm/pr-subscribers-clang

Author: Utkarsh Saxena (usx95)

Changes

Lifetime-bound analysis of reference parameters of coroutines and coroutine wrappers is helpful in surfacing memory bugs associated with using temporaries and stack variables in call expressions in plain return statements.

This is the default semantics of [[clang::coro_lifetimebound]]. But it should be okay to relax the requirements for a function when the reference arguments are not lifetime bound. For example:

A coroutine wrapper accepts a reference parameter but does not pass it to the underlying coroutine call.

[[clang::coro_wrapper]] Task&lt;int&gt; wrapper(const Request&amp; req) {
  return req.shouldCallA() ? coroA() : coroB();
}

Or passes it the coroutine by value

Task&lt;int&gt; coro(std::string s) { co_return s.size(); }
[[clang::coro_wrapper]] wrapper(const std::string&amp; s) { return coro(s); }

This patch allows functions to be annotated with [[clang::coro_not_lifetimebound]] to disable lifetime bound analysis for all calls to this function.


One missing piece here is a note suggesting using this annotation in cases of lifetime warnings. This would require some more tweaks in the lifetimebound analysis to recognize violations involving coroutines only.


Full diff: https://github.com/llvm/llvm-project/pull/76818.diff

6 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/include/clang/Basic/Attr.td (+8)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+22-4)
  • (modified) clang/lib/Sema/SemaInit.cpp (+2-1)
  • (modified) clang/test/Misc/pragma-attribute-supported-attributes-list.test (+1)
  • (modified) clang/test/SemaCXX/coro-lifetimebound.cpp (+15)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ee211c16a48ac8..88a0bf7d005dab 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -357,6 +357,7 @@ Attribute Changes in Clang
 - 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]]``.
+  This analysis can be disabled for a function by annotating the function with ``[[clang::coro_not_lifetimebound]]``.
 
 Improvements to Clang's diagnostics
 -----------------------------------
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index db17211747b17d..3dd3cb305dc4ff 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1121,6 +1121,14 @@ def CoroLifetimeBound : InheritableAttr {
   let SimpleHandler = 1;
 }
 
+def CoroNotLifetimeBound : InheritableAttr {
+  let Spellings = [Clang<"coro_not_lifetimebound">];
+  let Subjects = SubjectList<[Function]>;
+  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 98a7ecc7fd7df3..8ef6f55cba4c98 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -7671,9 +7671,12 @@ 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]]``.
+All parameters of a function are considered to be lifetime bound if the function returns a
+coroutine return type (CRT) annotated with ``[[clang::coro_lifetimebound]]``.
+This lifetime bound analysis can be disabled for a coroutine wrapper or a coroutine by annotating the function
+with ``[[clang::coro_not_lifetimebound]]`` function attribute .
+See `documentation`_ of ``[[clang::lifetimebound]]`` for details about lifetime bound analysis.
+
 
 Reference parameters of a coroutine are susceptible to capturing references to temporaries or local variables.
 
@@ -7703,7 +7706,7 @@ Both coroutines and coroutine wrappers are part of this analysis.
   };
 
   Task<int> coro(const int& a) { co_return a + 1; }
-  Task<int> [[clang::coro_wrapper]] coro_wrapper(const int& a, const int& b) {
+  [[clang::coro_wrapper]] Task<int> coro_wrapper(const int& a, const int& b) {
     return a > b ? coro(a) : coro(b);
   }
   Task<int> temporary_reference() {
@@ -7718,6 +7721,21 @@ Both coroutines and coroutine wrappers are part of this analysis.
     return coro(a); // warning: returning address of stack variable `a`.
   }
 
+This analysis can be disabled for all calls to a particular function by annotating the function
+with function attribute ``[[clang::coro_not_lifetimebound]]``.
+For example, this could be useful for coroutine wrappers which accept reference parameters
+but do not pass them to the underlying coroutine or pass them by value.
+
+.. code-block:: c++
+
+  Task<int> coro(int a) { co_return a + 1; }
+  [[clang::coro_wrapper, clang::coro_not_lifetimebound]] Task<int> coro_wrapper(const int& a) {
+    return coro(a + 1);
+  }
+  void use() {
+    auto task = coro_wrapper(1); // use of temporary is fine as the argument is not lifetime bound.
+  }
+
 .. _`documentation`: https://clang.llvm.org/docs/AttributeReference.html#lifetimebound
 .. _`CRT`: https://clang.llvm.org/docs/AttributeReference.html#coro-return-type
 }];
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 61d244f3bb9798..130aa5d04b82f2 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -7581,7 +7581,8 @@ static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call,
   bool CheckCoroCall = false;
   if (const auto *RD = Callee->getReturnType()->getAsRecordDecl()) {
     CheckCoroCall = RD->hasAttr<CoroLifetimeBoundAttr>() &&
-                    RD->hasAttr<CoroReturnTypeAttr>();
+                    RD->hasAttr<CoroReturnTypeAttr>() &&
+                    !Callee->hasAttr<CoroNotLifetimeBoundAttr>();
   }
   for (unsigned I = 0,
                 N = std::min<unsigned>(Callee->getNumParams(), Args.size());
diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 7b0cda0bca078d..88dd60ea584609 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -58,6 +58,7 @@
 // CHECK-NEXT: ConsumableSetOnRead (SubjectMatchRule_record)
 // CHECK-NEXT: Convergent (SubjectMatchRule_function)
 // CHECK-NEXT: CoroLifetimeBound (SubjectMatchRule_record)
+// CHECK-NEXT: CoroNotLifetimeBound (SubjectMatchRule_function)
 // CHECK-NEXT: CoroOnlyDestroyWhenComplete (SubjectMatchRule_record)
 // CHECK-NEXT: CoroReturnType (SubjectMatchRule_record)
 // CHECK-NEXT: CoroWrapper (SubjectMatchRule_function)
diff --git a/clang/test/SemaCXX/coro-lifetimebound.cpp b/clang/test/SemaCXX/coro-lifetimebound.cpp
index b4dc029a139848..dce909b3e41a9b 100644
--- a/clang/test/SemaCXX/coro-lifetimebound.cpp
+++ b/clang/test/SemaCXX/coro-lifetimebound.cpp
@@ -115,3 +115,18 @@ CoNoCRT<int> bar(int a) {
   co_return 1;
 }
 } // namespace not_a_crt
+
+// =============================================================================
+// Not lifetime bound coroutine wrappers: [[clang::coro_not_lifetimebound]].
+// =============================================================================
+namespace not_lifetimebound {
+Co<int> foo(int x) {  co_return x; }
+
+[[clang::coro_wrapper, clang::coro_not_lifetimebound]] 
+Co<int> foo_wrapper(const int& x) { return foo(x); }
+
+[[clang::coro_wrapper]] Co<int> caller() {
+  // The call to foo_wrapper is wrapper is safe.
+  return foo_wrapper(1);
+}
+} // namespace not_lifetimebound
\ No newline at end of file

@usx95 usx95 added the coroutines C++20 coroutines label Jan 3, 2024
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]]``.
All parameters of a function are considered to be lifetime bound if the function returns a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be useful to have an attribute like this on individual parameters?
I suspect it might happen in practice that some, but not all, parameters have this property. It's probably pretty rare, but I would ask people writing coroutine code to get their opinion. (We could always generalize to parameters later if the answer is inconclusive).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have asked a person for their input regarding parameter attributes. IMO this can be added later as a followup, as the function attribute is definitely necessary for brevity (as compared to annotating all parameters).

(Keeping this open for others to comment.)

@usx95 usx95 requested a review from ChuanqiXu9 January 3, 2024 14:28
@usx95 usx95 changed the title [coroutines] Introduce [[clang::coro_not_lifetimebound]] [coroutines] Introduce [[clang::coro_disable_lifetime_bound]] Jan 3, 2024
@usx95 usx95 changed the title [coroutines] Introduce [[clang::coro_disable_lifetime_bound]] [coroutines] Introduce [[clang::coro_disable_lifetimebound]] Jan 3, 2024
@usx95 usx95 requested a review from ilya-biryukov January 5, 2024 07:47
Copy link
Member

@ChuanqiXu9 ChuanqiXu9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel good with this. We can do other improvements in other patches.

Copy link
Contributor

@ilya-biryukov ilya-biryukov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree we could add parameter attributes later! LGTM!

@usx95 usx95 merged commit 190a75b into llvm:main Jan 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category coroutines C++20 coroutines
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants