[clang] Do not discard cleanups while processing immediate invocation
authorMariya Podchishchaeva <mariya.podchishchaeva@intel.com>
Fri, 30 Jun 2023 08:21:43 +0000 (04:21 -0400)
committerMariya Podchishchaeva <mariya.podchishchaeva@intel.com>
Fri, 30 Jun 2023 08:50:08 +0000 (04:50 -0400)
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 https://github.com/llvm/llvm-project/issues/60709

Reviewed By: ilya-biryukov

Differential Revision: https://reviews.llvm.org/D153962

clang/docs/ReleaseNotes.rst
clang/lib/Sema/SemaExpr.cpp
clang/test/CodeGenCXX/consteval-cleanup.cpp [new file with mode: 0644]
clang/test/SemaCXX/consteval-cleanup.cpp [new file with mode: 0644]

index da4ddff..4a9a030 100644 (file)
@@ -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
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
index 528b765..4d6414e 100644 (file)
@@ -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(),
diff --git a/clang/test/CodeGenCXX/consteval-cleanup.cpp b/clang/test/CodeGenCXX/consteval-cleanup.cpp
new file mode 100644 (file)
index 0000000..e6fdd50
--- /dev/null
@@ -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
+}
diff --git a/clang/test/SemaCXX/consteval-cleanup.cpp b/clang/test/SemaCXX/consteval-cleanup.cpp
new file mode 100644 (file)
index 0000000..ddc5617
--- /dev/null
@@ -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();
+}