https://github.com/yuxuanchen1997 created 
https://github.com/llvm/llvm-project/pull/196597

Fixes https://github.com/llvm/llvm-project/issues/196469

When Clang rebuilds a default member initializer for CWG1815 lifetime 
extension, TreeTransform's initializer path can drop CXXBindTemporaryExpr 
cleanup information. That loses destructor cleanup for ordinary temporaries 
inside the initializer; for a DMI-local lambda with an init-capture, the 
closure temporary is not destroyed at the end of the full-expression.

Handle CXXBindTemporaryExpr explicitly while rebuilding these initializers, 
rebind transformed subexpressions with MaybeBindToTemporary, and remember 
whether the rebuilt initializer still needs non-lifetime-extended cleanups. 
After discarding the cleanups collected for lifetime extension, restore the 
ExprWithCleanups marker only when such a rebuilt temporary remains.

When MaybeBindToTemporary references an implicit destructor and Sema has 
synthesized its body, pass that declaration to the AST consumer because there 
may be no later top-level definition point for DMI-local closure types. Add a 
CodeGenCXX regression test for a lambda init-capture in a default member 
initializer.

Assisted By: OpenAI Codex

>From b2588b33d516a7ad48ce1701879d447ff360efb3 Mon Sep 17 00:00:00 2001
From: Yuxuan Chen <[email protected]>
Date: Thu, 7 May 2026 20:24:49 -0700
Subject: [PATCH] [Clang] Track temporary cleanups in rebuilt default member
 initializers

Fixes https://github.com/llvm/llvm-project/issues/196469

When Clang rebuilds a default member initializer for CWG1815 lifetime 
extension, TreeTransform's initializer path can drop CXXBindTemporaryExpr 
cleanup information. That loses destructor cleanup for ordinary temporaries 
inside the initializer; for a DMI-local lambda with an init-capture, the 
closure temporary is not destroyed at the end of the full-expression.

Handle CXXBindTemporaryExpr explicitly while rebuilding these initializers, 
rebind transformed subexpressions with MaybeBindToTemporary, and remember 
whether the rebuilt initializer still needs non-lifetime-extended cleanups. 
After discarding the cleanups collected for lifetime extension, restore the 
ExprWithCleanups marker only when such a rebuilt temporary remains.

When MaybeBindToTemporary references an implicit destructor and Sema has 
synthesized its body, pass that declaration to the AST consumer because there 
may be no later top-level definition point for DMI-local closure types. Add a 
CodeGenCXX regression test for a lambda init-capture in a default member 
initializer.

Assisted By: OpenAI Codex
---
 clang/lib/Sema/SemaExpr.cpp                   | 55 ++++++++++++++++++-
 clang/lib/Sema/SemaExprCXX.cpp                |  9 +++
 ...469-default-member-init-lambda-cleanup.cpp | 24 ++++++++
 3 files changed, 87 insertions(+), 1 deletion(-)
 create mode 100644 
clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp

diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 9fd8c6a0a5451..ff6ce49c0d04b 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5703,11 +5703,54 @@ struct ImmediateCallVisitor : 
DynamicRecursiveASTVisitor {
 
 struct EnsureImmediateInvocationInDefaultArgs
     : TreeTransform<EnsureImmediateInvocationInDefaultArgs> {
+  using Base = TreeTransform<EnsureImmediateInvocationInDefaultArgs>;
+
   EnsureImmediateInvocationInDefaultArgs(Sema &SemaRef)
       : TreeTransform(SemaRef) {}
 
   bool AlwaysRebuild() { return true; }
 
+  bool rebuiltInitNeedsCleanups() const { return RebuiltInitNeedsCleanups; }
+
+  ExprResult TransformCXXBindTemporaryExpr(CXXBindTemporaryExpr *E) {
+    // TransformInitializer normally strips CXXBindTemporaryExpr. In default
+    // member initializers, rebuild the binding explicitly so CodeGen still
+    // knows which rebuilt temporaries need end-of-full-expression destruction.
+    ExprResult SubExpr = Base::TransformExpr(E->getSubExpr());
+    if (SubExpr.isInvalid())
+      return ExprError();
+    if (SubExpr.get() == E->getSubExpr()) {
+      if (!SuppressRebuiltTemporaryCleanup)
+        RebuiltInitNeedsCleanups = true;
+      return E;
+    }
+
+    ExprResult Res = SemaRef.MaybeBindToTemporary(SubExpr.get());
+    if (!SuppressRebuiltTemporaryCleanup && !Res.isInvalid())
+      RebuiltInitNeedsCleanups = true;
+    return Res;
+  }
+
+  ExprResult TransformInitializer(Expr *Init, bool NotCopyInit) {
+    Expr *UnwrappedInit = Init;
+    if (auto *FE = dyn_cast_if_present<FullExpr>(UnwrappedInit))
+      UnwrappedInit = FE->getSubExpr();
+
+    if (auto *MTE =
+            dyn_cast_if_present<MaterializeTemporaryExpr>(UnwrappedInit);
+        MTE && MTE->getExtendingDecl()) {
+      // The lifetime-extended temporary is intentionally not cleaned up at the
+      // end of the default member initializer full-expression.
+      llvm::SaveAndRestore SaveSuppress(SuppressRebuiltTemporaryCleanup, true);
+      return Base::TransformInitializer(Init, NotCopyInit);
+    }
+
+    if (auto *E = dyn_cast_if_present<CXXBindTemporaryExpr>(UnwrappedInit))
+      return TransformCXXBindTemporaryExpr(E);
+
+    return Base::TransformInitializer(Init, NotCopyInit);
+  }
+
   // Lambda can only have immediate invocations in the default
   // args of their parameters, which is transformed upon calling the closure.
   // The body is not a subexpression, so we have nothing to do.
@@ -5741,6 +5784,10 @@ struct EnsureImmediateInvocationInDefaultArgs
     return getDerived().RebuildSourceLocExpr(
         E->getIdentKind(), E->getType(), E->getBeginLoc(), E->getEndLoc(), DC);
   }
+
+private:
+  bool RebuiltInitNeedsCleanups = false;
+  bool SuppressRebuiltTemporaryCleanup = false;
 };
 
 ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
@@ -5882,6 +5929,8 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation 
Loc, FieldDecl *Field) {
   if (!NestedDefaultChecking)
     V.TraverseDecl(Field);
 
+  bool RebuiltInitNeedsCleanups = false;
+
   // CWG1815
   // Support lifetime extension of temporary created by aggregate
   // initialization using a default member initializer. We should rebuild
@@ -5914,6 +5963,7 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation 
Loc, FieldDecl *Field) {
       Field->setInvalidDecl();
       return ExprError();
     }
+    RebuiltInitNeedsCleanups = Immediate.rebuiltInitNeedsCleanups();
     Init = Res.get();
   }
 
@@ -5923,8 +5973,11 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation 
Loc, FieldDecl *Field) {
       runWithSufficientStackSpace(Loc, [&] {
         MarkDeclarationsReferencedInExpr(E, /*SkipLocalVariables=*/false);
       });
-    if (isInLifetimeExtendingContext())
+    if (isInLifetimeExtendingContext()) {
       DiscardCleanupsInEvaluationContext();
+      if (RebuiltInitNeedsCleanups)
+        Cleanup.setExprNeedsCleanups(true);
+    }
     // C++11 [class.base.init]p7:
     //   The initialization of each base and member constitutes a
     //   full-expression.
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 39c5e3b0671bb..0731f3289952c 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -13,6 +13,7 @@
 
 #include "TreeTransform.h"
 #include "TypeLocBuilder.h"
+#include "clang/AST/ASTConsumer.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/ASTLambda.h"
 #include "clang/AST/CXXInheritance.h"
@@ -6722,7 +6723,15 @@ ExprResult Sema::MaybeBindToTemporary(Expr *E) {
   CXXDestructorDecl *Destructor = IsDecltype ? nullptr : LookupDestructor(RD);
 
   if (Destructor) {
+    bool HadBody = Destructor->doesThisDeclarationHaveABody();
     MarkFunctionReferenced(E->getExprLoc(), Destructor);
+    // MarkFunctionReferenced can synthesize an implicit destructor. If that
+    // happens here, there may be no later top-level definition callback for
+    // CodeGen to see, for example for a closure type in a default member
+    // initializer.
+    if (Destructor->isImplicit() && !HadBody &&
+        Destructor->doesThisDeclarationHaveABody())
+      Consumer.HandleTopLevelDecl(DeclGroupRef(Destructor));
     CheckDestructorAccess(E->getExprLoc(), Destructor,
                           PDiag(diag::err_access_dtor_temp)
                             << E->getType());
diff --git 
a/clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp 
b/clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp
new file mode 100644
index 0000000000000..71857dc449416
--- /dev/null
+++ b/clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp
@@ -0,0 +1,24 @@
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm -o - 
%s | FileCheck %s
+
+struct Noisy {
+  Noisy();
+  ~Noisy();
+};
+
+struct Function {
+  template <typename F> Function(F) {}
+};
+
+struct Options {
+  Function function{[noisy = Noisy{}] {}};
+};
+
+Options kOptions{};
+
+// CHECK-LABEL: define internal void @__cxx_global_var_init
+// CHECK: call void @_ZN5NoisyC1Ev
+// CHECK: call void @_ZN8FunctionC1IN7Options8functionMUlvE_EEET_
+// CHECK: call void @_ZN7Options8functionMUlvE_D1Ev
+
+// CHECK-LABEL: define {{.*}} @_ZN7Options8functionMUlvE_D2Ev
+// CHECK: call void @_ZN5NoisyD1Ev

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to