Skip to content

Commit 550fa4e

Browse files
committed
[clang] Do not discard cleanups while processing immediate invocation
Since an immediate invocation is a full expression itself - it requires an additional ExprWithCleanups node, but it can participate to a bigger full expression which actually requires cleanups to be run after. Thanks @ilya-biryukov for helping reducing the reproducer and confirming that the analysis is correct. Fixes #60709 Reviewed By: ilya-biryukov Differential Revision: https://reviews.llvm.org/D153962
1 parent 3a9ea6a commit 550fa4e

File tree

4 files changed

+105
-1
lines changed

4 files changed

+105
-1
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,10 @@ Bug Fixes in This Version
557557
attempting to see if it could be type template for class template argument
558558
deduction. This fixes
559559
(`Issue 57495 <https://github.com/llvm/llvm-project/issues/57495>`_)
560+
- Fix missing destructor calls and therefore memory leaks in generated code
561+
when an immediate invocation appears as a part of an expression that produces
562+
temporaries.
563+
(`#60709 <https://github.com/llvm/llvm-project/issues/60709>`_).
560564

561565
Bug Fixes to Compiler Builtins
562566
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

clang/lib/Sema/SemaExpr.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18183,7 +18183,25 @@ ExprResult Sema::CheckForImmediateInvocation(ExprResult E, FunctionDecl *Decl) {
1818318183
return E;
1818418184
}
1818518185

18186-
E = MaybeCreateExprWithCleanups(E);
18186+
if (Cleanup.exprNeedsCleanups()) {
18187+
// Since an immediate invocation is a full expression itself - it requires
18188+
// an additional ExprWithCleanups node, but it can participate to a bigger
18189+
// full expression which actually requires cleanups to be run after so
18190+
// create ExprWithCleanups without using MaybeCreateExprWithCleanups as it
18191+
// may discard cleanups for outer expression too early.
18192+
18193+
// Note that ExprWithCleanups created here must always have empty cleanup
18194+
// objects:
18195+
// - compound literals do not create cleanup objects in C++ and immediate
18196+
// invocations are C++-only.
18197+
// - blocks are not allowed inside constant expressions and compiler will
18198+
// issue an error if they appear there.
18199+
//
18200+
// Hence, in correct code any cleanup objects created inside current
18201+
// evaluation context must be outside the immediate invocation.
18202+
E = ExprWithCleanups::Create(getASTContext(), E.get(),
18203+
Cleanup.cleanupsHaveSideEffects(), {});
18204+
}
1818718205

1818818206
ConstantExpr *Res = ConstantExpr::Create(
1818918207
getASTContext(), E.get(),
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s
2+
3+
struct P {
4+
consteval P() {}
5+
};
6+
7+
struct A {
8+
A(int v) { this->data = new int(v); }
9+
~A() { delete data; }
10+
private:
11+
int *data;
12+
};
13+
14+
void foo() {
15+
for (;A(1), P(), false;);
16+
// CHECK: foo
17+
// CHECK: for.cond:
18+
// CHECK: call void @_ZN1AC1Ei
19+
// CHECK: call void @_ZN1AD1Ev
20+
// CHECK: for.body
21+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// RUN: %clang_cc1 -fblocks -Wno-unused-value -std=c++20 -ast-dump -verify %s -ast-dump | FileCheck %s
2+
3+
// expected-no-diagnostics
4+
5+
struct P {
6+
consteval P() {}
7+
};
8+
9+
struct A {
10+
A(int v) { this->data = new int(v); }
11+
const int& get() const {
12+
return *this->data;
13+
}
14+
~A() { delete data; }
15+
private:
16+
int *data;
17+
};
18+
19+
void foo() {
20+
for (;A(1), P(), false;);
21+
// CHECK: foo
22+
// CHECK: ExprWithCleanups
23+
// CHECK-NEXT: BinaryOperator {{.*}} 'bool' ','
24+
// CHECK-NEXT: BinaryOperator {{.*}} 'P':'P' ','
25+
// CHECK-NEXT: CXXFunctionalCastExpr {{.*}} 'A':'A'
26+
// CHECK-NEXT: CXXBindTemporaryExpr {{.*}} 'A':'A'
27+
// CHECK-NEXT: CXXConstructExpr {{.*}} 'A':'A'
28+
// CHECK: ConstantExpr {{.*}} 'P':'P'
29+
// CHECK-NEXT: value:
30+
// CHECK-NEXT: ExprWithCleanups
31+
}
32+
33+
void foobar() {
34+
A a(1);
35+
for (; ^{ auto ptr = &a.get(); }(), P(), false;);
36+
// CHECK: ExprWithCleanups
37+
// CHECK-NEXT: cleanup Block
38+
// CHECK-NEXT: BinaryOperator {{.*}} 'bool' ','
39+
// CHECK-NEXT: BinaryOperator {{.*}} 'P':'P' ','
40+
// CHECK-NEXT: CallExpr
41+
// CHECK-NEXT: BlockExpr
42+
// CHECK: ConstantExpr {{.*}} 'P':'P'
43+
// CHECK-NEXT: value:
44+
// CHECK-NEXT: ExprWithCleanups
45+
// CHECK-NOT: cleanup Block
46+
}
47+
48+
struct B {
49+
int *p = new int(38);
50+
consteval int get() { return *p; }
51+
constexpr ~B() { delete p; }
52+
};
53+
54+
void bar() {
55+
// CHECK: bar
56+
// CHECK: ExprWithCleanups
57+
// CHECK: ConstantExpr
58+
// CHECK-NEXT: value:
59+
// CHECK-NEXT: ExprWithCleanups
60+
int k = B().get();
61+
}

0 commit comments

Comments
 (0)