[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