Skip to content

Commit 190a75b

Browse files
authored
[coroutines] Introduce [[clang::coro_disable_lifetimebound]] (#76818)
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. ```cpp [[clang::coro_wrapper]] Task<int> wrapper(const Request& req) { return req.shouldCallA() ? coroA() : coroB(); } ``` Or passes it the coroutine by value ```cpp 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.
1 parent 2a1e390 commit 190a75b

File tree

6 files changed

+49
-5
lines changed

6 files changed

+49
-5
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ Attribute Changes in Clang
357357
- Clang now introduced ``[[clang::coro_lifetimebound]]`` attribute.
358358
All parameters of a function are considered to be lifetime bound if the function
359359
returns a type annotated with ``[[clang::coro_lifetimebound]]`` and ``[[clang::coro_return_type]]``.
360+
This analysis can be disabled for a function by annotating the function with ``[[clang::coro_disable_lifetimebound]]``.
360361

361362
Improvements to Clang's diagnostics
362363
-----------------------------------

clang/include/clang/Basic/Attr.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,14 @@ def CoroLifetimeBound : InheritableAttr {
11211121
let SimpleHandler = 1;
11221122
}
11231123

1124+
def CoroDisableLifetimeBound : InheritableAttr {
1125+
let Spellings = [Clang<"coro_disable_lifetimebound">];
1126+
let Subjects = SubjectList<[Function]>;
1127+
let LangOpts = [CPlusPlus];
1128+
let Documentation = [CoroLifetimeBoundDoc];
1129+
let SimpleHandler = 1;
1130+
}
1131+
11241132
// OSObject-based attributes.
11251133
def OSConsumed : InheritableParamAttr {
11261134
let Spellings = [Clang<"os_consumed">];

clang/include/clang/Basic/AttrDocs.td

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7671,9 +7671,12 @@ The ``[[clang::coro_lifetimebound]]`` is a class attribute which can be applied
76717671
to a coroutine return type (`CRT`_) (i.e.
76727672
it should also be annotated with ``[[clang::coro_return_type]]``).
76737673

7674-
All parameters of a function are considered to be lifetime bound. See `documentation`_
7675-
of ``[[clang::lifetimebound]]`` for more details.
7676-
if the function returns a coroutine return type (CRT) annotated with ``[[clang::coro_lifetimebound]]``.
7674+
All parameters of a function are considered to be lifetime bound if the function returns a
7675+
coroutine return type (CRT) annotated with ``[[clang::coro_lifetimebound]]``.
7676+
This lifetime bound analysis can be disabled for a coroutine wrapper or a coroutine by annotating the function
7677+
with ``[[clang::coro_disable_lifetimebound]]`` function attribute .
7678+
See `documentation`_ of ``[[clang::lifetimebound]]`` for details about lifetime bound analysis.
7679+
76777680

76787681
Reference parameters of a coroutine are susceptible to capturing references to temporaries or local variables.
76797682

@@ -7703,7 +7706,7 @@ Both coroutines and coroutine wrappers are part of this analysis.
77037706
};
77047707

77057708
Task<int> coro(const int& a) { co_return a + 1; }
7706-
Task<int> [[clang::coro_wrapper]] coro_wrapper(const int& a, const int& b) {
7709+
[[clang::coro_wrapper]] Task<int> coro_wrapper(const int& a, const int& b) {
77077710
return a > b ? coro(a) : coro(b);
77087711
}
77097712
Task<int> temporary_reference() {
@@ -7718,6 +7721,21 @@ Both coroutines and coroutine wrappers are part of this analysis.
77187721
return coro(a); // warning: returning address of stack variable `a`.
77197722
}
77207723

7724+
This analysis can be disabled for all calls to a particular function by annotating the function
7725+
with function attribute ``[[clang::coro_disable_lifetimebound]]``.
7726+
For example, this could be useful for coroutine wrappers which accept reference parameters
7727+
but do not pass them to the underlying coroutine or pass them by value.
7728+
7729+
.. code-block:: c++
7730+
7731+
Task<int> coro(int a) { co_return a + 1; }
7732+
[[clang::coro_wrapper, clang::coro_disable_lifetimebound]] Task<int> coro_wrapper(const int& a) {
7733+
return coro(a + 1);
7734+
}
7735+
void use() {
7736+
auto task = coro_wrapper(1); // use of temporary is fine as the argument is not lifetime bound.
7737+
}
7738+
77217739
.. _`documentation`: https://clang.llvm.org/docs/AttributeReference.html#lifetimebound
77227740
.. _`CRT`: https://clang.llvm.org/docs/AttributeReference.html#coro-return-type
77237741
}];

clang/lib/Sema/SemaInit.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7581,7 +7581,8 @@ static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call,
75817581
bool CheckCoroCall = false;
75827582
if (const auto *RD = Callee->getReturnType()->getAsRecordDecl()) {
75837583
CheckCoroCall = RD->hasAttr<CoroLifetimeBoundAttr>() &&
7584-
RD->hasAttr<CoroReturnTypeAttr>();
7584+
RD->hasAttr<CoroReturnTypeAttr>() &&
7585+
!Callee->hasAttr<CoroDisableLifetimeBoundAttr>();
75857586
}
75867587
for (unsigned I = 0,
75877588
N = std::min<unsigned>(Callee->getNumParams(), Args.size());

clang/test/Misc/pragma-attribute-supported-attributes-list.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
// CHECK-NEXT: ConsumableAutoCast (SubjectMatchRule_record)
5858
// CHECK-NEXT: ConsumableSetOnRead (SubjectMatchRule_record)
5959
// CHECK-NEXT: Convergent (SubjectMatchRule_function)
60+
// CHECK-NEXT: CoroDisableLifetimeBound (SubjectMatchRule_function)
6061
// CHECK-NEXT: CoroLifetimeBound (SubjectMatchRule_record)
6162
// CHECK-NEXT: CoroOnlyDestroyWhenComplete (SubjectMatchRule_record)
6263
// CHECK-NEXT: CoroReturnType (SubjectMatchRule_record)

clang/test/SemaCXX/coro-lifetimebound.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,18 @@ CoNoCRT<int> bar(int a) {
115115
co_return 1;
116116
}
117117
} // namespace not_a_crt
118+
119+
// =============================================================================
120+
// Not lifetime bound coroutine wrappers: [[clang::coro_disable_lifetimebound]].
121+
// =============================================================================
122+
namespace disable_lifetimebound {
123+
Co<int> foo(int x) { co_return x; }
124+
125+
[[clang::coro_wrapper, clang::coro_disable_lifetimebound]]
126+
Co<int> foo_wrapper(const int& x) { return foo(x); }
127+
128+
[[clang::coro_wrapper]] Co<int> caller() {
129+
// The call to foo_wrapper is wrapper is safe.
130+
return foo_wrapper(1);
131+
}
132+
} // namespace disable_lifetimebound

0 commit comments

Comments
 (0)