-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[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
Conversation
@llvm/pr-subscribers-coroutines @llvm/pr-subscribers-clang Author: Utkarsh Saxena (usx95) ChangesLifetime-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 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 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:
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
|
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 |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.)
There was a problem hiding this 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.
There was a problem hiding this 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!
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.
Or passes it the coroutine by value
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.