Skip to content

Commit

Permalink
[clang] Do not discard cleanups while processing immediate invocation
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Fznamznon committed Jun 30, 2023
1 parent 3a9ea6a commit 550fa4e
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 1 deletion.
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,10 @@ Bug Fixes in This Version
attempting to see if it could be type template for class template argument
deduction. This fixes
(`Issue 57495 <https://github.com/llvm/llvm-project/issues/57495>`_)
- Fix missing destructor calls and therefore memory leaks in generated code
when an immediate invocation appears as a part of an expression that produces
temporaries.
(`#60709 <https://github.com/llvm/llvm-project/issues/60709>`_).

Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
20 changes: 19 additions & 1 deletion clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18183,7 +18183,25 @@ ExprResult Sema::CheckForImmediateInvocation(ExprResult E, FunctionDecl *Decl) {
return E;
}

E = MaybeCreateExprWithCleanups(E);
if (Cleanup.exprNeedsCleanups()) {
// 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 so
// create ExprWithCleanups without using MaybeCreateExprWithCleanups as it
// may discard cleanups for outer expression too early.

// Note that ExprWithCleanups created here must always have empty cleanup
// objects:
// - compound literals do not create cleanup objects in C++ and immediate
// invocations are C++-only.
// - blocks are not allowed inside constant expressions and compiler will
// issue an error if they appear there.
//
// Hence, in correct code any cleanup objects created inside current
// evaluation context must be outside the immediate invocation.
E = ExprWithCleanups::Create(getASTContext(), E.get(),
Cleanup.cleanupsHaveSideEffects(), {});
}

ConstantExpr *Res = ConstantExpr::Create(
getASTContext(), E.get(),
Expand Down
21 changes: 21 additions & 0 deletions clang/test/CodeGenCXX/consteval-cleanup.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s

struct P {
consteval P() {}
};

struct A {
A(int v) { this->data = new int(v); }
~A() { delete data; }
private:
int *data;
};

void foo() {
for (;A(1), P(), false;);
// CHECK: foo
// CHECK: for.cond:
// CHECK: call void @_ZN1AC1Ei
// CHECK: call void @_ZN1AD1Ev
// CHECK: for.body
}
61 changes: 61 additions & 0 deletions clang/test/SemaCXX/consteval-cleanup.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// RUN: %clang_cc1 -fblocks -Wno-unused-value -std=c++20 -ast-dump -verify %s -ast-dump | FileCheck %s

// expected-no-diagnostics

struct P {
consteval P() {}
};

struct A {
A(int v) { this->data = new int(v); }
const int& get() const {
return *this->data;
}
~A() { delete data; }
private:
int *data;
};

void foo() {
for (;A(1), P(), false;);
// CHECK: foo
// CHECK: ExprWithCleanups
// CHECK-NEXT: BinaryOperator {{.*}} 'bool' ','
// CHECK-NEXT: BinaryOperator {{.*}} 'P':'P' ','
// CHECK-NEXT: CXXFunctionalCastExpr {{.*}} 'A':'A'
// CHECK-NEXT: CXXBindTemporaryExpr {{.*}} 'A':'A'
// CHECK-NEXT: CXXConstructExpr {{.*}} 'A':'A'
// CHECK: ConstantExpr {{.*}} 'P':'P'
// CHECK-NEXT: value:
// CHECK-NEXT: ExprWithCleanups
}

void foobar() {
A a(1);
for (; ^{ auto ptr = &a.get(); }(), P(), false;);
// CHECK: ExprWithCleanups
// CHECK-NEXT: cleanup Block
// CHECK-NEXT: BinaryOperator {{.*}} 'bool' ','
// CHECK-NEXT: BinaryOperator {{.*}} 'P':'P' ','
// CHECK-NEXT: CallExpr
// CHECK-NEXT: BlockExpr
// CHECK: ConstantExpr {{.*}} 'P':'P'
// CHECK-NEXT: value:
// CHECK-NEXT: ExprWithCleanups
// CHECK-NOT: cleanup Block
}

struct B {
int *p = new int(38);
consteval int get() { return *p; }
constexpr ~B() { delete p; }
};

void bar() {
// CHECK: bar
// CHECK: ExprWithCleanups
// CHECK: ConstantExpr
// CHECK-NEXT: value:
// CHECK-NEXT: ExprWithCleanups
int k = B().get();
}

0 comments on commit 550fa4e

Please sign in to comment.