Latest diff:
f1ab4c2677..adf9bc902b
We address two additional bugs here:
### Problem 1: Deactivated normal cleanup still runs, leading to
double-free
Consider the following:
```cpp
struct A { };
struct B { B(const A&); };
struct S {
A a;
B b;
};
int AcceptS(S s);
void Accept2(int x, int y);
void Test() {
Accept2(AcceptS({.a = A{}, .b = A{}}), ({ return; 0; }));
}
```
We add cleanups as follows:
1. push dtor for field `S::a`
2. push dtor for temp `A{}` (used by ` B(const A&)` in `.b = A{}`)
3. push dtor for field `S::b`
4. Deactivate 3 `S::b`-> This pops the cleanup.
5. Deactivate 1 `S::a` -> Does not pop the cleanup as *2* is top. Should
create _active flag_!!
6. push dtor for `~S()`.
7. ...
It is important to deactivate **5** using active flags. Without the
active flags, the `return` will fallthrough it and would run both `~S()`
and dtor `S::a` leading to **double free** of `~A()`.
In this patch, we unconditionally emit active flags while deactivating
normal cleanups. These flags are deleted later by the `AllocaTracker` if
the cleanup is not emitted.
### Problem 2: Missing cleanup for conditional lifetime extension
We push 2 cleanups for lifetime-extended cleanup. The first cleanup is
useful if we exit from the middle of the expression (stmt-expr/coro
suspensions). This is deactivated after full-expr, and a new cleanup is
pushed, extending the lifetime of the temporaries (to the scope of the
reference being initialized).
If this lifetime extension happens to be conditional, then we use active
flags to remember whether the branch was taken and if the object was
initialized.
Previously, we used a **single** active flag, which was used by both
cleanups. This is wrong because the first cleanup will be forced to
deactivate after the full-expr and therefore this **active** flag will
always be **inactive**. The dtor for the lifetime extended entity would
not run as it always sees an **inactive** flag.
In this patch, we solve this using two separate active flags for both
cleanups. Both of them are activated if the conditional branch is taken,
but only one of them is deactivated after the full-expr.
---
Fixes https://github.com/llvm/llvm-project/issues/63818
Fixes https://github.com/llvm/llvm-project/issues/88478
---
Previous PR logs:
1. https://github.com/llvm/llvm-project/pull/85398
2. https://github.com/llvm/llvm-project/pull/88670
3. https://github.com/llvm/llvm-project/pull/88751
4. https://github.com/llvm/llvm-project/pull/88884
94 lines
4.2 KiB
C++
94 lines
4.2 KiB
C++
// RUN: %clang_cc1 --std=c++20 -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s
|
|
|
|
#include "Inputs/coroutine.h"
|
|
|
|
struct Printy {
|
|
Printy(const char *name) : name(name) {}
|
|
~Printy() {}
|
|
const char *name;
|
|
};
|
|
|
|
struct coroutine {
|
|
struct promise_type;
|
|
std::coroutine_handle<promise_type> handle;
|
|
~coroutine() {
|
|
if (handle) handle.destroy();
|
|
}
|
|
};
|
|
|
|
struct coroutine::promise_type {
|
|
coroutine get_return_object() {
|
|
return {std::coroutine_handle<promise_type>::from_promise(*this)};
|
|
}
|
|
std::suspend_never initial_suspend() noexcept { return {}; }
|
|
std::suspend_always final_suspend() noexcept { return {}; }
|
|
void return_void() {}
|
|
void unhandled_exception() {}
|
|
};
|
|
|
|
struct Awaiter : std::suspend_always {
|
|
Printy await_resume() { return {"awaited"}; }
|
|
};
|
|
|
|
int foo() { return 2; }
|
|
|
|
coroutine ArrayInitCoro() {
|
|
// Verify that:
|
|
// - We do the necessary stores for array cleanups.
|
|
// - Array cleanups are called by await.cleanup.
|
|
// - We activate the cleanup after the first element and deactivate it in await.ready (see cleanup.isactive).
|
|
|
|
// CHECK-LABEL: define dso_local void @_Z13ArrayInitCorov
|
|
// CHECK: %arrayinit.endOfInit = alloca ptr, align 8
|
|
// CHECK: %cleanup.isactive = alloca i1, align 1
|
|
Printy arr[2] = {
|
|
Printy("a"),
|
|
// CHECK: %arrayinit.begin = getelementptr inbounds [2 x %struct.Printy], ptr %arr.reload.addr, i64 0, i64 0
|
|
// CHECK-NEXT: %arrayinit.begin.spill.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 10
|
|
// CHECK-NEXT: store ptr %arrayinit.begin, ptr %arrayinit.begin.spill.addr, align 8
|
|
// CHECK-NEXT: store i1 true, ptr %cleanup.isactive.reload.addr, align 1
|
|
// CHECK-NEXT: store ptr %arrayinit.begin, ptr %arrayinit.endOfInit.reload.addr, align 8
|
|
// CHECK-NEXT: call void @_ZN6PrintyC1EPKc(ptr noundef nonnull align 8 dereferenceable(8) %arrayinit.begin, ptr noundef @.str)
|
|
// CHECK-NEXT: %arrayinit.element = getelementptr inbounds %struct.Printy, ptr %arrayinit.begin, i64 1
|
|
// CHECK-NEXT: %arrayinit.element.spill.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 11
|
|
// CHECK-NEXT: store ptr %arrayinit.element, ptr %arrayinit.element.spill.addr, align 8
|
|
// CHECK-NEXT: store ptr %arrayinit.element, ptr %arrayinit.endOfInit.reload.addr, align 8
|
|
co_await Awaiter{}
|
|
// CHECK-NEXT: @_ZNSt14suspend_always11await_readyEv
|
|
// CHECK-NEXT: br i1 %{{.+}}, label %await.ready, label %CoroSave30
|
|
};
|
|
// CHECK: await.cleanup: ; preds = %AfterCoroSuspend{{.*}}
|
|
// CHECK-NEXT: br label %cleanup{{.*}}.from.await.cleanup
|
|
|
|
// CHECK: cleanup{{.*}}.from.await.cleanup: ; preds = %await.cleanup
|
|
// CHECK: br label %cleanup{{.*}}
|
|
|
|
// CHECK: await.ready:
|
|
// CHECK-NEXT: %arrayinit.element.reload.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 11
|
|
// CHECK-NEXT: %arrayinit.element.reload = load ptr, ptr %arrayinit.element.reload.addr, align 8
|
|
// CHECK-NEXT: call void @_ZN7Awaiter12await_resumeEv
|
|
// CHECK-NEXT: store i1 false, ptr %cleanup.isactive.reload.addr, align 1
|
|
// CHECK-NEXT: br label %cleanup{{.*}}.from.await.ready
|
|
|
|
// CHECK: cleanup{{.*}}: ; preds = %cleanup{{.*}}.from.await.ready, %cleanup{{.*}}.from.await.cleanup
|
|
// CHECK: %cleanup.is_active = load i1, ptr %cleanup.isactive.reload.addr, align 1
|
|
// CHECK-NEXT: br i1 %cleanup.is_active, label %cleanup.action, label %cleanup.done
|
|
|
|
// CHECK: cleanup.action:
|
|
// CHECK: %arraydestroy.isempty = icmp eq ptr %arrayinit.begin.reload{{.*}}, %{{.*}}
|
|
// CHECK-NEXT: br i1 %arraydestroy.isempty, label %arraydestroy.done{{.*}}, label %arraydestroy.body.from.cleanup.action
|
|
// Ignore rest of the array cleanup.
|
|
}
|
|
|
|
coroutine ArrayInitWithCoReturn() {
|
|
// CHECK-LABEL: define dso_local void @_Z21ArrayInitWithCoReturnv
|
|
// Verify that we start to emit the array destructor.
|
|
// CHECK: %arrayinit.endOfInit = alloca ptr, align 8
|
|
Printy arr[2] = {"a", ({
|
|
if (foo()) {
|
|
co_return;
|
|
}
|
|
"b";
|
|
})};
|
|
}
|