[clang] [CIR][EH] Fix scope for partial array cleanup (PR #227838)

Andy Kaylor via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 30 20:47:39 PDT 2026


https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/227838

>From e958177ae380e9bad71feaa993a62cab15317f30 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Wed, 30 Sep 2026 11:39:51 -0700
Subject: [PATCH] [CIR][EH] Fix scope for partial array cleanup

There was a bug in CIR where if an array whose elements required destruction
was initialized with an ILE, we weren't properly closing the EH cleanup
scope after the initialization completed, so it enclosed the rest of the
function. The result was that if anything later in the function threw an
exception, it would trigger both the normal destruction of the array and the
leftover EH "partial" cleanup, leading to a double-free.

This change fixes that problem by introducing a CleanupDeactivationScope
around the init list processing (where we already had a MissingFeature
marker saying this was needed). The EH cleanup scope is now closed when
the CleanupDeactivationScope object goes out of scope.

This change also caused some observable changes to existing tests where we
were previously behaving incorrectly.

Assisted-by: Cursor / various models
---
 clang/include/clang/CIR/MissingFeatures.h     |   1 -
 clang/lib/CIR/CodeGen/CIRGenDecl.cpp          |   6 +-
 clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp |   6 +-
 .../test/CIR/CodeGen/new-array-in-ternary.cpp |  22 +-
 .../CIR/CodeGen/partial-array-cleanup.cpp     | 225 ++++++++++++------
 5 files changed, 169 insertions(+), 91 deletions(-)

diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 491ee9d9ae2ca..9a866b62849ef 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -205,7 +205,6 @@ struct MissingFeatures {
   static bool checkBitfieldClipping() { return false; }
   static bool cirgenABIInfo() { return false; }
   static bool cleanupAfterErrorDiags() { return false; }
-  static bool cleanupDeactivationScope() { return false; }
   static bool cleanupsToDeactivate() { return false; }
   static bool constEmitterArrayILE() { return false; }
   static bool constEmitterVectorILE() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index 451f6f8af7fd1..097cf88e026c5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -1170,8 +1170,8 @@ struct IrregularPartialArrayDestroy final : EHScopeStack::Cleanup {
 } // namespace
 
 /// Push an EH cleanup to destroy already-constructed elements of the given
-/// array.  The cleanup may be popped with deactivateCleanupBlock or
-/// popCleanupBlock.
+/// array. The cleanup is deactivated when the enclosing
+/// CleanupDeactivationScope exits.
 ///
 /// \param elementType - the immediate element type of the array;
 ///   possibly still an array type
@@ -1180,7 +1180,7 @@ void CIRGenFunction::pushIrregularPartialArrayCleanup(mlir::Value arrayBegin,
                                                       QualType elementType,
                                                       CharUnits elementAlign,
                                                       Destroyer *destroyer) {
-  ehStack.pushCleanup<IrregularPartialArrayDestroy>(
+  pushCleanupAndDeferDeactivation<IrregularPartialArrayDestroy>(
       EHCleanup, arrayBegin, arrayEndPointer, elementType, elementAlign,
       destroyer);
 }
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
index 562002424a16f..81a6eae7effcf 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
@@ -736,11 +736,11 @@ void AggExprEmitter::emitArrayInit(Address destPtr, cir::ArrayType arrayTy,
   const CharUnits elementAlign =
       destPtr.getAlignment().alignmentOfArrayElement(elementSize);
 
-  // Exception safety requires us to destroy all the already-constructed
-  // members if an initializer throws. For that, we'll need an EH cleanup.
+  // Destroy already-constructed elements if a later initializer throws.
+  // The cleanup is deactivated when this initialization finishes.
   QualType::DestructionKind dtorKind = elementType.isDestructedType();
   Address endOfInit = Address::invalid();
-  assert(!cir::MissingFeatures::cleanupDeactivationScope());
+  CIRGenFunction::CleanupDeactivationScope deactivateCleanups(cgf);
 
   if (dtorKind && cgf.getLangOpts().Exceptions) {
     endOfInit = cgf.createTempAlloca(cirElementPtrType, cgf.getPointerAlign(),
diff --git a/clang/test/CIR/CodeGen/new-array-in-ternary.cpp b/clang/test/CIR/CodeGen/new-array-in-ternary.cpp
index 25fa87aa4cbb3..6e5061453d786 100644
--- a/clang/test/CIR/CodeGen/new-array-in-ternary.cpp
+++ b/clang/test/CIR/CodeGen/new-array-in-ternary.cpp
@@ -56,27 +56,19 @@ DoubleAPFloat::DoubleAPFloat()
 // CIR-EH:         cir.store %[[FALSE_INIT]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool>
 // CIR-EH:         %[[RESULT:.*]] = cir.ternary({{.*}}, true {
 // CIR-EH:           %[[NEW_RESULT:.*]] = cir.alloca "__new_result" {{.*}} : !cir.ptr<!cir.ptr<!rec_APFloat>>
-// CIR-EH:           %[[SPILL:.*]] = cir.alloca "tmp.exprcleanup" {{.*}} : !cir.ptr<!cir.ptr<!rec_APFloat>>
 // CIR-EH:           cir.call @_Znam
 // CIR-EH:           cir.cleanup.scope {
 // CIR-EH:             %[[TRUE:.*]] = cir.const #true
 // CIR-EH:             cir.store %[[TRUE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool>
-// CIR-EH:             cir.cleanup.scope {
-// CIR-EH:               %[[FALSE:.*]] = cir.const #false
-// CIR-EH:               cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool>
-// CIR-EH:               cir.store {{.*}} %{{.*}}, %[[SPILL]] : !cir.ptr<!rec_APFloat>, !cir.ptr<!cir.ptr<!rec_APFloat>>
-// CIR-EH:               cir.yield
-// CIR-EH:             } cleanup eh {
-// CIR-EH:               cir.call @_ZN7APFloatD1Ev
-// CIR-EH:             }
-// CIR-EH:             cir.yield
+// CIR-EH:             cir.store {{.*}} %{{.*}}, %[[NEW_RESULT]] : !cir.ptr<!rec_APFloat>, !cir.ptr<!cir.ptr<!rec_APFloat>>
+// CIR-EH-NOT:         cir.call @_ZN7APFloatD1Ev
 // CIR-EH:           } cleanup eh {
 // CIR-EH:             %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]]
 // CIR-EH:             cir.if %[[IS_ACTIVE]] {
 // CIR-EH:               cir.call @_ZdaPvm
 // CIR-EH:             }
 // CIR-EH:           }
-// CIR-EH:           %[[RELOAD:.*]] = cir.load {{.*}} %[[SPILL]] : !cir.ptr<!cir.ptr<!rec_APFloat>>, !cir.ptr<!rec_APFloat>
+// CIR-EH:           %[[RELOAD:.*]] = cir.load {{.*}} %[[NEW_RESULT]] : !cir.ptr<!cir.ptr<!rec_APFloat>>, !cir.ptr<!rec_APFloat>
 // CIR-EH:           cir.yield %[[RELOAD]] : !cir.ptr<!rec_APFloat>
 // CIR-EH:         }, false {
 // CIR-EH:           %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!rec_APFloat>
@@ -96,7 +88,6 @@ DoubleAPFloat::DoubleAPFloat()
 // LLVM-EH:         %[[ALLOC:.*]] = call {{.*}} ptr @_Znam(i64 {{.*}} 8)
 // LLVMCIR-EH:      store i8 1, ptr %[[ACTIVE]]
 // OGCG-EH:         store i1 true, ptr %[[ACTIVE]]
-// LLVMCIR-EH:      store i8 0, ptr %[[ACTIVE]]
 // LLVM-EH:         br label %{{.*}}
 // LLVM-EH:       [[FALSE_BR]]:
 // LLVM-EH:         br label %{{.*}}
@@ -138,11 +129,11 @@ T &pickRef(bool b) {
 
 // CIR-EH-LABEL: cir.func {{.*}} @_Z7pickRefb
 // CIR-EH:         %[[RES:.*]] = cir.ternary({{.*}}, true {
-// CIR-EH:           %[[SPILL:.*]] = cir.alloca "tmp.exprcleanup" {{.*}} : !cir.ptr<!cir.ptr<!rec_T>>
+// CIR-EH:           %[[NEW_RESULT:.*]] = cir.alloca "__new_result" {{.*}} : !cir.ptr<!cir.ptr<!rec_T>>
 // CIR-EH:           cir.call @_Znam
 // CIR-EH:           cir.cleanup.scope {
 // CIR-EH:             cir.cleanup.scope {
-// CIR-EH:               cir.store {{.*}} %{{.*}}, %[[SPILL]] : !cir.ptr<!rec_T>, !cir.ptr<!cir.ptr<!rec_T>>
+// CIR-EH:               cir.store {{.*}} %{{.*}}, %{{.*}} : !cir.ptr<!rec_T>, !cir.ptr<!cir.ptr<!rec_T>>
 // CIR-EH:               cir.yield
 // CIR-EH:             } cleanup eh {
 // CIR-EH:               cir.call @_ZN1TD1Ev
@@ -151,7 +142,7 @@ T &pickRef(bool b) {
 // CIR-EH:           } cleanup eh {
 // CIR-EH:             cir.call @_ZdaPvm
 // CIR-EH:           }
-// CIR-EH:           %[[RELOAD:.*]] = cir.load {{.*}} %[[SPILL]] : !cir.ptr<!cir.ptr<!rec_T>>, !cir.ptr<!rec_T>
+// CIR-EH:           %[[RELOAD:.*]] = cir.load {{.*}} %[[NEW_RESULT]] : !cir.ptr<!cir.ptr<!rec_T>>, !cir.ptr<!rec_T>
 // CIR-EH:           cir.yield %[[RELOAD]] : !cir.ptr<!rec_T>
 // CIR-EH:         }, false {
 // CIR-EH:           %[[GREF:.*]] = cir.get_global @globalT : !cir.ptr<!rec_T>
@@ -171,7 +162,6 @@ T &pickRef(bool b) {
 // LLVM-EH:         %[[ALLOC:.*]] = call {{.*}} ptr @_Znam(i64 {{.*}} 10)
 // LLVMCIR-EH:      store i8 1, ptr %[[ACTIVE]]
 // OGCG-EH:         store i1 true, ptr %[[ACTIVE]]
-// LLVMCIR-EH:      store i8 0, ptr %[[ACTIVE]]
 // LLVM-EH:         br label %{{.*}}
 // LLVM-EH:       [[FALSE_BR]]:
 // LLVM-EH:         br label %{{.*}}
diff --git a/clang/test/CIR/CodeGen/partial-array-cleanup.cpp b/clang/test/CIR/CodeGen/partial-array-cleanup.cpp
index 3cdf30906f36e..2fe74012873e3 100644
--- a/clang/test/CIR/CodeGen/partial-array-cleanup.cpp
+++ b/clang/test/CIR/CodeGen/partial-array-cleanup.cpp
@@ -825,18 +825,17 @@ void test_multi_dim_init_list_partial_array_cleanup() {
     S arr[2][2] = { {S(), S()}, {S(), S()} };
 }
 
-// There are three nested EH cleanups here, innermost first:
-//  - row 1's S[2] init list (elementType S, already non-array)
-//  - row 0's S[2] init list (elementType S, already non-array)
-//  - the outer S[2][2] init list (elementType S[2], STILL an array --
-//    this is the one that needs the bitcast down to S)
+// Each row's partial-array EH cleanup ends when that row is initialized.
+// Row 1's cleanup is a sibling of row 0's, inside the outer S[2][2]
+// cleanup. The outer element type is still S[2], so that cleanup
+// bitcasts down to S. The array destructor follows initialization.
 // CIR-BEFORE-LPP-LABEL: cir.func {{.*}} @_Z46test_multi_dim_init_list_partial_array_cleanupv()
 // CIR-BEFORE-LPP:       %[[ARR:.*]] = cir.alloca "arr" {{.*}} init : !cir.ptr<!cir.array<!cir.array<!rec_S x 2> x 2>>
 // CIR-BEFORE-LPP:       %[[OUTER_END:.*]] = cir.alloca "arrayinit.endOfInit" {{.*}} : !cir.ptr<!cir.ptr<!cir.array<!rec_S x 2>>>
 // CIR-BEFORE-LPP:       %[[ROW0_END:.*]] = cir.alloca "arrayinit.endOfInit" {{.*}} : !cir.ptr<!cir.ptr<!rec_S>>
+// CIR-BEFORE-LPP:       %[[ROW0_ITER:.*]] = cir.alloca "__array_idx" {{.*}} : !cir.ptr<!cir.ptr<!rec_S>>
 // CIR-BEFORE-LPP:       %[[ROW1_END:.*]] = cir.alloca "arrayinit.endOfInit" {{.*}} : !cir.ptr<!cir.ptr<!rec_S>>
 // CIR-BEFORE-LPP:       %[[ROW1_ITER:.*]] = cir.alloca "__array_idx" {{.*}} : !cir.ptr<!cir.ptr<!rec_S>>
-// CIR-BEFORE-LPP:       %[[ROW0_ITER:.*]] = cir.alloca "__array_idx" {{.*}} : !cir.ptr<!cir.ptr<!rec_S>>
 // CIR-BEFORE-LPP:       %[[OUTER_ITER:.*]] = cir.alloca "__array_idx" {{.*}} : !cir.ptr<!cir.ptr<!rec_S>>
 // CIR-BEFORE-LPP:       %[[ROW0_BEGIN:.*]] = cir.cast array_to_ptrdecay %[[ARR]] : !cir.ptr<!cir.array<!cir.array<!rec_S x 2> x 2>> -> !cir.ptr<!cir.array<!rec_S x 2>>
 // CIR-BEFORE-LPP:       cir.store {{.*}} %[[ROW0_BEGIN]], %[[OUTER_END]] : !cir.ptr<!cir.array<!rec_S x 2>>, !cir.ptr<!cir.ptr<!cir.array<!rec_S x 2>>>
@@ -849,39 +848,8 @@ void test_multi_dim_init_list_partial_array_cleanup() {
 // CIR-BEFORE-LPP:           %[[ROW0_ELT1:.*]] = cir.ptr_stride %[[ROW0_ELT0]], %{{.*}} : (!cir.ptr<!rec_S>, !s64i) -> !cir.ptr<!rec_S>
 // CIR-BEFORE-LPP:           cir.store {{.*}} %[[ROW0_ELT1]], %[[ROW0_END]]
 // CIR-BEFORE-LPP:           cir.call @_ZN1SC1Ev(%[[ROW0_ELT1]])
-// CIR-BEFORE-LPP:           %[[ROW1_BEGIN:.*]] = cir.ptr_stride %[[ROW0_BEGIN]], %{{.*}} : (!cir.ptr<!cir.array<!rec_S x 2>>, !s64i) -> !cir.ptr<!cir.array<!rec_S x 2>>
-// CIR-BEFORE-LPP:           cir.store {{.*}} %[[ROW1_BEGIN]], %[[OUTER_END]]
-// CIR-BEFORE-LPP:           %[[ROW1_ELT0:.*]] = cir.cast array_to_ptrdecay %[[ROW1_BEGIN]] : !cir.ptr<!cir.array<!rec_S x 2>> -> !cir.ptr<!rec_S>
-// CIR-BEFORE-LPP:           cir.store {{.*}} %[[ROW1_ELT0]], %[[ROW1_END]]
-// CIR-BEFORE-LPP:           cir.cleanup.scope {
-//                             --- row 1: S(), S() ---
-// CIR-BEFORE-LPP:             cir.call @_ZN1SC1Ev(%[[ROW1_ELT0]])
-// CIR-BEFORE-LPP:             %[[ROW1_ELT1:.*]] = cir.ptr_stride %[[ROW1_ELT0]], %{{.*}} : (!cir.ptr<!rec_S>, !s64i) -> !cir.ptr<!rec_S>
-// CIR-BEFORE-LPP:             cir.store {{.*}} %[[ROW1_ELT1]], %[[ROW1_END]]
-// CIR-BEFORE-LPP:             cir.call @_ZN1SC1Ev(%[[ROW1_ELT1]])
-// CIR-BEFORE-LPP:             cir.yield
-//                             --- innermost EH cleanup (row 1): elementType is already S, no bitcast ---
-// CIR-BEFORE-LPP:           } cleanup eh {
-// CIR-BEFORE-LPP:             %[[ROW1_EH_END:.*]] = cir.load {{.*}} %[[ROW1_END]] : !cir.ptr<!cir.ptr<!rec_S>>, !cir.ptr<!rec_S>
-// CIR-BEFORE-LPP:             %[[ROW1_EH_NE:.*]] = cir.cmp ne %[[ROW1_EH_END]], %[[ROW1_ELT0]] : !cir.ptr<!rec_S>
-// CIR-BEFORE-LPP:             cir.if %[[ROW1_EH_NE]] {
-// CIR-BEFORE-LPP:               cir.store {{.*}} %[[ROW1_EH_END]], %[[ROW1_ITER]]
-// CIR-BEFORE-LPP:               cir.do {
-// CIR-BEFORE-LPP:                 %[[ROW1_EH_CUR:.*]] = cir.load {{.*}} %[[ROW1_ITER]] : !cir.ptr<!cir.ptr<!rec_S>>, !cir.ptr<!rec_S>
-// CIR-BEFORE-LPP:                 %[[ROW1_EH_PREV:.*]] = cir.ptr_stride %[[ROW1_EH_CUR]], %{{.*}} : (!cir.ptr<!rec_S>, !s64i) -> !cir.ptr<!rec_S>
-// CIR-BEFORE-LPP:                 cir.store {{.*}} %[[ROW1_EH_PREV]], %[[ROW1_ITER]]
-// CIR-BEFORE-LPP:                 cir.call @_ZN1SD1Ev(%[[ROW1_EH_PREV]]) nothrow
-// CIR-BEFORE-LPP:                 cir.yield
-// CIR-BEFORE-LPP:               } while {
-// CIR-BEFORE-LPP:                 %[[ROW1_EH_CUR2:.*]] = cir.load {{.*}} %[[ROW1_ITER]] : !cir.ptr<!cir.ptr<!rec_S>>, !cir.ptr<!rec_S>
-// CIR-BEFORE-LPP:                 %[[ROW1_EH_CONT:.*]] = cir.cmp ne %[[ROW1_EH_CUR2]], %[[ROW1_ELT0]] : !cir.ptr<!rec_S>
-// CIR-BEFORE-LPP:                 cir.condition(%[[ROW1_EH_CONT]])
-// CIR-BEFORE-LPP:               }
-// CIR-BEFORE-LPP:             }
-// CIR-BEFORE-LPP:             cir.yield
-// CIR-BEFORE-LPP:           }
 // CIR-BEFORE-LPP:           cir.yield
-//                           --- middle EH cleanup (row 0): elementType is already S, no bitcast ---
+//                           --- row 0 EH cleanup ends before row 1 starts ---
 // CIR-BEFORE-LPP:         } cleanup eh {
 // CIR-BEFORE-LPP:           %[[ROW0_EH_END:.*]] = cir.load {{.*}} %[[ROW0_END]] : !cir.ptr<!cir.ptr<!rec_S>>, !cir.ptr<!rec_S>
 // CIR-BEFORE-LPP:           %[[ROW0_EH_NE:.*]] = cir.cmp ne %[[ROW0_EH_END]], %[[ROW0_ELT0]] : !cir.ptr<!rec_S>
@@ -901,10 +869,40 @@ void test_multi_dim_init_list_partial_array_cleanup() {
 // CIR-BEFORE-LPP:           }
 // CIR-BEFORE-LPP:           cir.yield
 // CIR-BEFORE-LPP:         }
+// CIR-BEFORE-LPP:         %[[ROW1_BEGIN:.*]] = cir.ptr_stride %[[ROW0_BEGIN]], %{{.*}} : (!cir.ptr<!cir.array<!rec_S x 2>>, !s64i) -> !cir.ptr<!cir.array<!rec_S x 2>>
+// CIR-BEFORE-LPP:         cir.store {{.*}} %[[ROW1_BEGIN]], %[[OUTER_END]]
+// CIR-BEFORE-LPP:         %[[ROW1_ELT0:.*]] = cir.cast array_to_ptrdecay %[[ROW1_BEGIN]] : !cir.ptr<!cir.array<!rec_S x 2>> -> !cir.ptr<!rec_S>
+// CIR-BEFORE-LPP:         cir.store {{.*}} %[[ROW1_ELT0]], %[[ROW1_END]]
+// CIR-BEFORE-LPP:         cir.cleanup.scope {
+//                           --- row 1: S(), S() ---
+// CIR-BEFORE-LPP:           cir.call @_ZN1SC1Ev(%[[ROW1_ELT0]])
+// CIR-BEFORE-LPP:           %[[ROW1_ELT1:.*]] = cir.ptr_stride %[[ROW1_ELT0]], %{{.*}} : (!cir.ptr<!rec_S>, !s64i) -> !cir.ptr<!rec_S>
+// CIR-BEFORE-LPP:           cir.store {{.*}} %[[ROW1_ELT1]], %[[ROW1_END]]
+// CIR-BEFORE-LPP:           cir.call @_ZN1SC1Ev(%[[ROW1_ELT1]])
+// CIR-BEFORE-LPP:           cir.yield
+//                           --- row 1 EH cleanup: elementType is already S ---
+// CIR-BEFORE-LPP:         } cleanup eh {
+// CIR-BEFORE-LPP:           %[[ROW1_EH_END:.*]] = cir.load {{.*}} %[[ROW1_END]] : !cir.ptr<!cir.ptr<!rec_S>>, !cir.ptr<!rec_S>
+// CIR-BEFORE-LPP:           %[[ROW1_EH_NE:.*]] = cir.cmp ne %[[ROW1_EH_END]], %[[ROW1_ELT0]] : !cir.ptr<!rec_S>
+// CIR-BEFORE-LPP:           cir.if %[[ROW1_EH_NE]] {
+// CIR-BEFORE-LPP:             cir.store {{.*}} %[[ROW1_EH_END]], %[[ROW1_ITER]]
+// CIR-BEFORE-LPP:             cir.do {
+// CIR-BEFORE-LPP:               %[[ROW1_EH_CUR:.*]] = cir.load {{.*}} %[[ROW1_ITER]] : !cir.ptr<!cir.ptr<!rec_S>>, !cir.ptr<!rec_S>
+// CIR-BEFORE-LPP:               %[[ROW1_EH_PREV:.*]] = cir.ptr_stride %[[ROW1_EH_CUR]], %{{.*}} : (!cir.ptr<!rec_S>, !s64i) -> !cir.ptr<!rec_S>
+// CIR-BEFORE-LPP:               cir.store {{.*}} %[[ROW1_EH_PREV]], %[[ROW1_ITER]]
+// CIR-BEFORE-LPP:               cir.call @_ZN1SD1Ev(%[[ROW1_EH_PREV]]) nothrow
+// CIR-BEFORE-LPP:               cir.yield
+// CIR-BEFORE-LPP:             } while {
+// CIR-BEFORE-LPP:               %[[ROW1_EH_CUR2:.*]] = cir.load {{.*}} %[[ROW1_ITER]] : !cir.ptr<!cir.ptr<!rec_S>>, !cir.ptr<!rec_S>
+// CIR-BEFORE-LPP:               %[[ROW1_EH_CONT:.*]] = cir.cmp ne %[[ROW1_EH_CUR2]], %[[ROW1_ELT0]] : !cir.ptr<!rec_S>
+// CIR-BEFORE-LPP:               cir.condition(%[[ROW1_EH_CONT]])
+// CIR-BEFORE-LPP:             }
+// CIR-BEFORE-LPP:           }
+// CIR-BEFORE-LPP:           cir.yield
+// CIR-BEFORE-LPP:         }
 // CIR-BEFORE-LPP:         cir.yield
-//                         --- outer EH cleanup (S[2][2]): elementType is still S[2] here, so the
-//                             cleanup must bitcast down to the base non-array element type (S)
-//                             before comparing pointers and destroying -- this is the regression ---
+//                         --- outer EH cleanup (S[2][2]): elementType is still S[2], so the
+//                             cleanup bitcasts down to the base element type S ---
 // CIR-BEFORE-LPP:       } cleanup eh {
 // CIR-BEFORE-LPP:         %[[OUTER_EH_END:.*]] = cir.load {{.*}} %[[OUTER_END]] : !cir.ptr<!cir.ptr<!cir.array<!rec_S x 2>>>, !cir.ptr<!cir.array<!rec_S x 2>>
 // CIR-BEFORE-LPP:         %[[OUTER_EH_BEGIN_BASE:.*]] = cir.cast bitcast %[[ROW0_BEGIN]] : !cir.ptr<!cir.array<!rec_S x 2>> -> !cir.ptr<!rec_S>
@@ -926,8 +924,7 @@ void test_multi_dim_init_list_partial_array_cleanup() {
 // CIR-BEFORE-LPP:         }
 // CIR-BEFORE-LPP:         cir.yield
 // CIR-BEFORE-LPP:       }
-//                       --- normal (non-EH) cleanup: unconditional, uses the array.dtor sugar
-//                           over the fully-flattened S[4] view (this path was never buggy) ---
+//                       --- normal cleanup: array.dtor over the flattened S[4] view ---
 // CIR-BEFORE-LPP:       cir.cleanup.scope {
 // CIR-BEFORE-LPP:         cir.yield
 // CIR-BEFORE-LPP:       } cleanup all {
@@ -946,13 +943,14 @@ void test_multi_dim_init_list_partial_array_cleanup() {
 // CIR:       %[[BEGIN:.*]] = cir.cast array_to_ptrdecay %[[ARRAY]] : !cir.ptr<!cir.array<!cir.array<!rec_S x 2> x 2>> -> !cir.ptr<!cir.array<!rec_S x 2>>
 // CIR:       cir.store {{.*}} %[[BEGIN]], %[[END_OF_INIT]] : !cir.ptr<!cir.array<!rec_S x 2>>, !cir.ptr<!cir.ptr<!cir.array<!rec_S x 2>>>
 // CIR:       cir.cleanup.scope {
-//              --- explicit inits for both rows ---
+//              --- row 0, then its EH cleanup, then row 1 ---
 // CIR:         cir.call @_ZN1SC1Ev
 // CIR:         cir.call @_ZN1SC1Ev
+// CIR:       } cleanup eh {
+// CIR:         cir.call @_ZN1SD1Ev
 // CIR:         cir.call @_ZN1SC1Ev
 // CIR:         cir.call @_ZN1SC1Ev
-//              --- outer EH cleanup: drill the still-array elementType (S[2])
-//                  down to the base non-array element type (S) via bitcast ---
+//              --- outer EH cleanup: element type is still S[2], bitcast to S ---
 // CIR:       } cleanup eh {
 // CIR:         %[[END_VAL:.*]] = cir.load {{.*}} %[[END_OF_INIT]] : !cir.ptr<!cir.ptr<!cir.array<!rec_S x 2>>>, !cir.ptr<!cir.array<!rec_S x 2>>
 // CIR:         %[[BEGIN_BASE:.*]] = cir.cast bitcast %[[BEGIN]] : !cir.ptr<!cir.array<!rec_S x 2>> -> !cir.ptr<!rec_S>
@@ -995,11 +993,10 @@ void test_multi_dim_init_list_partial_array_cleanup() {
 
 // LLVM-LABEL: define {{.*}}@_Z46test_multi_dim_init_list_partial_array_cleanupv()
 // LLVM:      %[[ARR:.*]] = alloca [2 x [2 x %struct.S]]
-// LLVM:      %[[OUTER_END_A:.*]] = alloca ptr
 //
 //            --- row 0: element 0, element 1 ---
 // LLVM:      %[[ROW0_BEGIN:.*]] = getelementptr [2 x %struct.S], ptr %[[ARR]], i32 0
-// LLVM:      store ptr %[[ROW0_BEGIN]], ptr %[[OUTER_END_A]]
+// LLVM:      store ptr %[[ROW0_BEGIN]], ptr %[[OUTER_END_A:.*]]
 // LLVM:      %[[ROW0_ELT0:.*]] = getelementptr %struct.S, ptr %[[ROW0_BEGIN]], i32 0
 // LLVM:      store ptr %[[ROW0_ELT0]], ptr %[[ROW0_END_A:.*]]
 // LLVM:      invoke void @_ZN1SC1Ev(ptr {{.*}} %[[ROW0_ELT0]])
@@ -1010,6 +1007,24 @@ void test_multi_dim_init_list_partial_array_cleanup() {
 // LLVM:      invoke void @_ZN1SC1Ev(ptr {{.*}} %[[ROW0_ELT1]])
 // LLVM:        to label %{{.*}} unwind label %[[ROW0_LPAD]]
 //
+//            --- row 0 EH cleanup runs before row 1 is initialized ---
+// LLVM:      [[ROW0_LPAD]]:
+// LLVM:        landingpad { ptr, i32 }
+// LLVM:          cleanup
+// LLVM:        %[[ROW0_EH_END:.*]] = load ptr, ptr %[[ROW0_END_A]]
+// LLVM:        %[[ROW0_EH_NE:.*]] = icmp ne ptr %[[ROW0_EH_END]], %[[ROW0_ELT0]]
+// LLVM:        br i1 %[[ROW0_EH_NE]], label %[[ROW0_DTOR:.*]], label %{{.*}}
+//
+// LLVM:      [[ROW0_DTOR]]:
+// LLVM:        store ptr %[[ROW0_EH_END]], ptr %[[ROW0_ITER:.*]]
+// LLVM:        br label %[[ROW0_DTOR_BODY:.*]]
+//
+// LLVM:      [[ROW0_DTOR_BODY]]:
+// LLVM:        %[[ROW0_DCUR:.*]] = load ptr, ptr %[[ROW0_ITER]]
+// LLVM:        %[[ROW0_DPREV:.*]] = getelementptr %struct.S, ptr %[[ROW0_DCUR]], i64 -1
+// LLVM:        store ptr %[[ROW0_DPREV]], ptr %[[ROW0_ITER]]
+// LLVM:        call void @_ZN1SD1Ev(ptr {{.*}} %[[ROW0_DPREV]])
+//
 //            --- row 1: element 0, element 1 ---
 // LLVM:      %[[ROW1_BEGIN:.*]] = getelementptr [2 x %struct.S], ptr %[[ROW0_BEGIN]], i64 1
 // LLVM:      store ptr %[[ROW1_BEGIN]], ptr %[[OUTER_END_A]]
@@ -1023,7 +1038,7 @@ void test_multi_dim_init_list_partial_array_cleanup() {
 // LLVM:      invoke void @_ZN1SC1Ev(ptr {{.*}} %[[ROW1_ELT1]])
 // LLVM:        to label %{{.*}} unwind label %[[ROW1_LPAD]]
 //
-//            --- row 1 EH cleanup (elementType S, no bitcast needed) ---
+//            --- row 1 EH cleanup ---
 // LLVM:      [[ROW1_LPAD]]:
 // LLVM:        landingpad { ptr, i32 }
 // LLVM:          cleanup
@@ -1041,25 +1056,7 @@ void test_multi_dim_init_list_partial_array_cleanup() {
 // LLVM:        store ptr %[[ROW1_DPREV]], ptr %[[ROW1_ITER]]
 // LLVM:        call void @_ZN1SD1Ev(ptr {{.*}} %[[ROW1_DPREV]])
 //
-//            --- row 0 EH cleanup (elementType S, no bitcast needed) ---
-// LLVM:      [[ROW0_LPAD]]:
-// LLVM:        landingpad { ptr, i32 }
-// LLVM:          cleanup
-// LLVM:        %[[ROW0_EH_END:.*]] = load ptr, ptr %[[ROW0_END_A]]
-// LLVM:        %[[ROW0_EH_NE:.*]] = icmp ne ptr %[[ROW0_EH_END]], %[[ROW0_ELT0]]
-// LLVM:        br i1 %[[ROW0_EH_NE]], label %[[ROW0_DTOR:.*]], label %{{.*}}
-//
-// LLVM:      [[ROW0_DTOR]]:
-// LLVM:        store ptr %[[ROW0_EH_END]], ptr %[[ROW0_ITER:.*]]
-// LLVM:        br label %[[ROW0_DTOR_BODY:.*]]
-//
-// LLVM:      [[ROW0_DTOR_BODY]]:
-// LLVM:        %[[ROW0_DCUR:.*]] = load ptr, ptr %[[ROW0_ITER]]
-// LLVM:        %[[ROW0_DPREV:.*]] = getelementptr %struct.S, ptr %[[ROW0_DCUR]], i64 -1
-// LLVM:        store ptr %[[ROW0_DPREV]], ptr %[[ROW0_ITER]]
-// LLVM:        call void @_ZN1SD1Ev(ptr {{.*}} %[[ROW0_DPREV]])
-//
-//            --- outer EH cleanup
+//            --- outer EH cleanup, reached from either row ---
 // LLVM:        %[[OUTER_EH_END:.*]] = load ptr, ptr %[[OUTER_END_A]]
 // LLVM:        %[[OUTER_EH_NE:.*]] = icmp ne ptr %[[OUTER_EH_END]], %[[ROW0_BEGIN]]
 // LLVM:        br i1 %[[OUTER_EH_NE]], label %[[OUTER_DTOR:.*]], label %{{.*}}
@@ -1344,3 +1341,95 @@ void Temp2InArray() {
 // OGCG: %[[DTOR_ELT:.*]] = getelementptr inbounds %struct.CausesTemp2, ptr %{{.*}}, i64 -1
 // OGCG: call void @_ZN11CausesTemp2D1Ev(ptr {{.*}}%[[DTOR_ELT]])
 
+struct Agg {
+  int a;
+  const char *b;
+  S s;
+};
+
+void side_effect();
+
+// Aggregate initialization has no ExprWithCleanups around the
+// initializer. The partial-array EH cleanup still ends when
+// initialization finishes. side_effect() is covered by the array
+// variable's own destructor cleanup.
+void test_partial_cleanup_covers_init_only() {
+  Agg arr[2] = {{1, "a"}, {2, "b"}};
+  side_effect();
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z37test_partial_cleanup_covers_init_onlyv()
+// CIR:       cir.cleanup.scope {
+// CIR:         cir.call @_ZN1SC1Ev
+// CIR:         cir.call @_ZN1SC1Ev
+// CIR-NOT:     cir.call @_Z11side_effectv()
+// CIR:       } cleanup eh {
+// CIR:         cir.if %{{.*}} {
+// CIR:           cir.do {
+// CIR:             cir.call @_ZN3AggD1Ev
+// CIR:           } while {
+// CIR:           }
+// CIR:         }
+// CIR:       }
+// CIR:       cir.cleanup.scope {
+// CIR:         cir.call @_Z11side_effectv()
+// CIR:       } cleanup all {
+// CIR:         cir.do {
+// CIR:           cir.call @_ZN3AggD1Ev
+// CIR:         } while {
+// CIR:         }
+// CIR:       }
+
+// Both constructors unwind to the partial-array landing pad. The second
+// constructor's normal successor reaches side_effect(), which unwinds to
+// the full-array destructor.
+// LLVM-LABEL: define {{.*}} @_Z37test_partial_cleanup_covers_init_onlyv()
+// LLVM:         invoke void @_ZN1SC1Ev(ptr {{.*}})
+// LLVM:           to label %[[CONT1:.*]] unwind label %[[INIT_LPAD:.*]]
+// LLVM:       [[CONT1]]:
+// LLVM:         invoke void @_ZN1SC1Ev(ptr {{.*}})
+// LLVM:           to label %[[CONT2:.*]] unwind label %[[INIT_LPAD]]
+// LLVM:       [[CONT2]]:
+// LLVM:         br label %[[AFTER_INIT:.*]]
+// LLVM-NOT:     @_Z11side_effectv()
+// LLVM:       [[INIT_LPAD]]:
+// LLVM:         landingpad { ptr, i32 }
+// LLVM:           cleanup
+// LLVM:         icmp ne ptr %{{.*}}, %{{.*}}
+// LLVM:         call void @_ZN3AggD1Ev(ptr {{.*}})
+// LLVM:         resume { ptr, i32 }
+// LLVM:       [[AFTER_INIT]]:
+// LLVM:         br label %[[SIDE:.*]]
+// LLVM:       [[SIDE]]:
+// LLVM:         invoke void @_Z11side_effectv()
+// LLVM:           to label %{{.*}} unwind label %[[FULL_LPAD:.*]]
+// LLVM:         call void @_ZN3AggD1Ev(ptr {{.*}})
+// LLVM:       [[FULL_LPAD]]:
+// LLVM:         landingpad { ptr, i32 }
+// LLVM:           cleanup
+// LLVM:         call void @_ZN3AggD1Ev(ptr {{.*}})
+// LLVM:         resume { ptr, i32 }
+
+// The second constructor's continuation calls side_effect(). Original
+// codegen emits that normal path before the landing pads.
+// OGCG-LABEL: define {{.*}} @_Z37test_partial_cleanup_covers_init_onlyv()
+// OGCG:         invoke void @_ZN1SC1Ev(ptr {{.*}})
+// OGCG:           to label %[[CONT1:.*]] unwind label %[[INIT_LPAD:.*]]
+// OGCG:       [[CONT1]]:
+// OGCG:         invoke void @_ZN1SC1Ev(ptr {{.*}})
+// OGCG:           to label %[[CONT2:.*]] unwind label %[[INIT_LPAD]]
+// OGCG:       [[CONT2]]:
+// OGCG:         invoke void @_Z11side_effectv()
+// OGCG:           to label %{{.*}} unwind label %[[FULL_LPAD:.*]]
+// OGCG:         call void @_ZN3AggD1Ev(ptr {{.*}})
+// OGCG:       [[INIT_LPAD]]:
+// OGCG:         landingpad { ptr, i32 }
+// OGCG:           cleanup
+// OGCG:         icmp eq ptr %{{.*}}, %{{.*}}
+// OGCG:         call void @_ZN3AggD1Ev(ptr {{.*}})
+// OGCG:       [[FULL_LPAD]]:
+// OGCG:         landingpad { ptr, i32 }
+// OGCG:           cleanup
+// OGCG:         call void @_ZN3AggD1Ev(ptr {{.*}})
+// OGCG:         resume { ptr, i32 }
+



More information about the cfe-commits mailing list