Utkarsh Saxena d72146f471
Re-apply "Emit missing cleanups for stmt-expr" and other commits (#89154)
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
2024-04-29 12:33:46 +02:00

303 lines
8.1 KiB
C++

// RUN: %clang_cc1 %s -fblocks -triple x86_64-apple-darwin -emit-llvm -o - | FileCheck %s
// CHECK: @[[BLOCK_DESCRIPTOR22:.*]] = internal constant { i64, i64, ptr, ptr, ptr, ptr } { i64 0, i64 36, ptr @__copy_helper_block_8_32c22_ZTSN12_GLOBAL__N_11BE, ptr @__destroy_helper_block_8_32c22_ZTSN12_GLOBAL__N_11BE, ptr @{{.*}}, ptr null }, align 8
namespace test0 {
// CHECK-LABEL: define{{.*}} void @_ZN5test04testEi(
// CHECK: define internal void @___ZN5test04testEi_block_invoke{{.*}}(
// CHECK: define internal void @___ZN5test04testEi_block_invoke_2{{.*}}(
void test(int x) {
^{ ^{ (void) x; }; };
}
}
extern void (^out)();
namespace test1 {
// Capturing const objects doesn't require a local block.
// CHECK-LABEL: define{{.*}} void @_ZN5test15test1Ev()
// CHECK: store ptr @__block_literal_global{{.*}}, ptr @out
void test1() {
const int NumHorsemen = 4;
out = ^{ (void) NumHorsemen; };
}
// That applies to structs too...
// CHECK-LABEL: define{{.*}} void @_ZN5test15test2Ev()
// CHECK: store ptr @__block_literal_global{{.*}}, ptr @out
struct loc { double x, y; };
void test2() {
const loc target = { 5, 6 };
out = ^{ (void) target; };
}
// ...unless they have mutable fields...
// CHECK-LABEL: define{{.*}} void @_ZN5test15test3Ev()
// CHECK: [[BLOCK:%.*]] = alloca [[BLOCK_T:<{.*}>]],
// CHECK: store ptr [[BLOCK]], ptr @out
struct mut { mutable int x; };
void test3() {
const mut obj = { 5 };
out = ^{ (void) obj; };
}
// ...or non-trivial destructors...
// CHECK-LABEL: define{{.*}} void @_ZN5test15test4Ev()
// CHECK: [[OBJ:%.*]] = alloca
// CHECK: [[BLOCK:%.*]] = alloca [[BLOCK_T:<{.*}>]],
// CHECK: store ptr [[BLOCK]], ptr @out
struct scope { int x; ~scope(); };
void test4() {
const scope obj = { 5 };
out = ^{ (void) obj; };
}
// ...or non-trivial copy constructors, but it's not clear how to do
// that and still have a constant initializer in '03.
}
namespace test2 {
struct A {
A();
A(const A &);
~A();
};
struct B {
B();
B(const B &);
~B();
};
// CHECK-LABEL: define{{.*}} void @_ZN5test24testEv()
void test() {
__block A a;
__block B b;
^{ (void)a; (void)b; };
}
// CHECK-LABEL: define internal void @__Block_byref_object_copy
// CHECK: call void @_ZN5test21AC1ERKS0_(
// CHECK-LABEL: define internal void @__Block_byref_object_dispose
// CHECK: call void @_ZN5test21AD1Ev(
// CHECK-LABEL: define internal void @__Block_byref_object_copy
// CHECK: call void @_ZN5test21BC1ERKS0_(
// CHECK-LABEL: define internal void @__Block_byref_object_dispose
// CHECK: call void @_ZN5test21BD1Ev(
}
// Make sure we mark destructors for parameters captured in blocks.
namespace test3 {
struct A {
A(const A&);
~A();
};
struct B : A {
};
void test(B b) {
extern void consume(void(^)());
consume(^{ (void) b; });
}
}
namespace test4 {
struct A {
A();
~A();
};
void foo(A a);
void test() {
extern void consume(void(^)());
consume(^{ return foo(A()); });
}
// CHECK-LABEL: define{{.*}} void @_ZN5test44testEv()
// CHECK-LABEL: define internal void @___ZN5test44testEv_block_invoke
// CHECK: [[TMP:%.*]] = alloca [[A:%.*]], align 1
// CHECK-NEXT: store ptr [[BLOCKDESC:%.*]], ptr {{.*}}, align 8
// CHECK: call void @_ZN5test41AC1Ev(ptr {{[^,]*}} [[TMP]])
// CHECK-NEXT: call void @_ZN5test43fooENS_1AE(ptr noundef [[TMP]])
// CHECK-NEXT: call void @_ZN5test41AD1Ev(ptr {{[^,]*}} [[TMP]])
// CHECK-NEXT: ret void
}
namespace test5 {
struct A {
unsigned afield;
A();
A(const A&);
~A();
void foo() const;
};
void doWithBlock(void(^)());
void test(bool cond) {
A x;
void (^b)() = (cond ? ^{ x.foo(); } : (void(^)()) 0);
doWithBlock(b);
}
// CHECK-LABEL: define{{.*}} void @_ZN5test54testEb(
// CHECK: [[COND:%.*]] = alloca i8
// CHECK-NEXT: [[X:%.*]] = alloca [[A:%.*]], align 4
// CHECK-NEXT: [[B:%.*]] = alloca ptr, align 8
// CHECK-NEXT: [[BLOCK:%.*]] = alloca [[BLOCK_T:.*]], align 8
// CHECK-NEXT: [[COND_CLEANUP_SAVE:%.*]] = alloca ptr, align 8
// CHECK-NEXT: [[CLEANUP_ACTIVE:%.*]] = alloca i1
// CHECK-NEXT: [[T0:%.*]] = zext i1
// CHECK-NEXT: store i8 [[T0]], ptr [[COND]], align 1
// CHECK-NEXT: call void @_ZN5test51AC1Ev(ptr {{[^,]*}} [[X]])
// CHECK-NEXT: [[T0:%.*]] = load i8, ptr [[COND]], align 1
// CHECK-NEXT: [[T1:%.*]] = trunc i8 [[T0]] to i1
// CHECK-NEXT: store i1 false, ptr [[CLEANUP_ACTIVE]]
// CHECK-NEXT: br i1 [[T1]],
// CHECK-NOT: br
// CHECK: [[CAPTURE:%.*]] = getelementptr inbounds [[BLOCK_T]], ptr [[BLOCK]], i32 0, i32 5
// CHECK-NEXT: call void @_ZN5test51AC1ERKS0_(ptr {{[^,]*}} [[CAPTURE]], ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) [[X]])
// CHECK-NEXT: store ptr [[CAPTURE]], ptr [[COND_CLEANUP_SAVE]], align 8
// CHECK-NEXT: store i1 true, ptr [[CLEANUP_ACTIVE]]
// CHECK-NEXT: br label
// CHECK: br label
// CHECK: phi
// CHECK-NEXT: store
// CHECK-NEXT: load
// CHECK-NEXT: call void @_ZN5test511doWithBlockEU13block_pointerFvvE(
// CHECK-NEXT: [[T0:%.*]] = load i1, ptr [[CLEANUP_ACTIVE]]
// CHECK-NEXT: br i1 [[T0]]
// CHECK: [[T3:%.*]] = load ptr, ptr [[COND_CLEANUP_SAVE]], align 8
// CHECK-NEXT: call void @_ZN5test51AD1Ev(ptr {{[^,]*}} [[T3]])
// CHECK-NEXT: br label
// CHECK: call void @_ZN5test51AD1Ev(ptr {{[^,]*}} [[X]])
// CHECK-NEXT: ret void
}
namespace test6 {
struct A {
A();
~A();
};
void foo(const A &, void (^)());
void bar();
void test() {
// Make sure that the temporary cleanup isn't somehow captured
// within the block.
foo(A(), ^{ bar(); });
bar();
}
// CHECK-LABEL: define{{.*}} void @_ZN5test64testEv()
// CHECK: [[TEMP:%.*]] = alloca [[A:%.*]], align 1
// CHECK-NEXT: call void @_ZN5test61AC1Ev(ptr {{[^,]*}} [[TEMP]])
// CHECK-NEXT: call void @_ZN5test63fooERKNS_1AEU13block_pointerFvvE(
// CHECK-NEXT: call void @_ZN5test61AD1Ev(ptr {{[^,]*}} [[TEMP]])
// CHECK-NEXT: call void @_ZN5test63barEv()
// CHECK-NEXT: ret void
}
namespace test7 {
int f() {
static int n;
int *const p = &n;
return ^{ return *p; }();
}
}
namespace test8 {
// failure to capture this after skipping rebuild of the 'this' pointer.
struct X {
int x;
template<typename T>
int foo() {
return ^ { return x; }();
}
};
template int X::foo<int>();
}
namespace test9 {
struct B {
void *p;
B();
B(const B&);
~B();
};
void use_block(void (^)());
void use_block_2(void (^)(), const B &a);
// Ensuring that creating a non-trivial capture copy expression
// doesn't end up stealing the block registration for the block we
// just parsed. That block must have captures or else it won't
// force registration. Must occur within a block for some reason.
void test() {
B x;
use_block(^{
int y;
use_block_2(^{ (void)y; }, x);
});
}
}
namespace test10 {
// Check that 'v' is included in the copy helper function name to indicate
// the constructor taking a volatile parameter is called to copy the captured
// object.
// CHECK-LABEL: define linkonce_odr hidden void @__copy_helper_block_8_32c16_ZTSVN6test101BE(
// CHECK: call void @_ZN6test101BC1ERVKS0_(
// CHECK-LABEL: define linkonce_odr hidden void @__destroy_helper_block_8_32c16_ZTSVN6test101BE(
// CHECK: call void @_ZN6test101BD1Ev(
struct B {
int a;
B();
B(const B &);
B(const volatile B &);
~B();
};
void test() {
volatile B x;
^{ (void)x; };
}
}
// Copy/dispose helper functions and block descriptors of blocks that capture
// objects that are non-external and non-trivial have internal linkage.
// CHECK-LABEL: define internal void @_ZN12_GLOBAL__N_14testEv(
// CHECK: store ptr @[[BLOCK_DESCRIPTOR22]], ptr %{{.*}}, align 8
// CHECK-LABEL: define internal void @__copy_helper_block_8_32c22_ZTSN12_GLOBAL__N_11BE(
// CHECK-LABEL: define internal void @__destroy_helper_block_8_32c22_ZTSN12_GLOBAL__N_11BE(
namespace {
struct B {
int a;
B();
B(const B &);
~B();
};
void test() {
B x;
^{ (void)x; };
}
}
void callTest() {
test();
}