[all-commits] [llvm/llvm-project] 667e58: [coroutines][coro_lifetimebound] Detect lifetime i...
Utkarsh Saxena via All-commits
all-commits at lists.llvm.org
Thu Jan 18 02:57:07 PST 2024
Branch: refs/heads/main
Home: https://github.com/llvm/llvm-project
Commit: 667e58a72e0d81abe0ab3500b5d5563b6a598e7f
https://github.com/llvm/llvm-project/commit/667e58a72e0d81abe0ab3500b5d5563b6a598e7f
Author: Utkarsh Saxena <usx at google.com>
Date: 2024-01-18 (Thu, 18 Jan 2024)
Changed paths:
M clang/include/clang/Sema/Sema.h
M clang/lib/Sema/SemaCoroutine.cpp
M clang/lib/Sema/SemaDecl.cpp
M clang/lib/Sema/SemaInit.cpp
M clang/test/SemaCXX/coro-lifetimebound.cpp
M clang/test/SemaCXX/coro-return-type-and-wrapper.cpp
Log Message:
-----------
[coroutines][coro_lifetimebound] Detect lifetime issues with lambda captures (#77066)
### Problem
```cpp
co_task<int> coro() {
int a = 1;
auto lamb = [a]() -> co_task<int> {
co_return a; // 'a' in the lambda object dies after the iniital_suspend in the lambda coroutine.
}();
co_return co_await lamb;
}
```
[use-after-free](https://godbolt.org/z/GWPEovWWc)
Lambda captures (even by value) are prone to use-after-free once the
lambda object dies. In the above example, the lambda object appears only
as a temporary in the call expression. It dies after the first
suspension (`initial_suspend`) in the lambda.
On resumption in `co_await lamb`, the lambda accesses `a` which is part
of the already-dead lambda object.
---
### Solution
This problem can be formulated by saying that the `this` parameter of
the lambda call operator is a lifetimebound parameter. The lambda object
argument should therefore live atleast as long as the return object.
That said, this requirement does not hold if the lambda does not have a
capture list. In principle, the coroutine frame still has a reference to
a dead lambda object, but it is easy to see that the object would not be
used in the lambda-coroutine body due to no capture list.
It is safe to use this pattern inside a`co_await` expression due to the
lifetime extension of temporaries. Example:
```cpp
co_task<int> coro() {
int a = 1;
int res = co_await [a]() -> co_task<int> { co_return a; }();
co_return res;
}
```
---
### Background
This came up in the discussion with seastar folks on
[RFC](https://discourse.llvm.org/t/rfc-lifetime-bound-check-for-parameters-of-coroutines/74253/19?u=usx95).
This is a fairly common pattern in continuation-style-passing (CSP)
async programming involving futures and continuations. Document ["Lambda
coroutine
fiasco"](https://github.com/scylladb/seastar/blob/master/doc/lambda-coroutine-fiasco.md)
by Seastar captures the problem.
This pattern makes the migration from CSP-style async programming to
coroutines very bugprone.
Fixes https://github.com/llvm/llvm-project/issues/76995
---------
Co-authored-by: Chuanqi Xu <yedeng.yd at linux.alibaba.com>
More information about the All-commits
mailing list