[clang] [Clang] Track temporary cleanups in rebuilt default member initializers (PR #196597)

2026-06-10 Thread Yuxuan Chen via cfe-commits

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

>From 190653df6b4480e824a079a27089ed847fcb7fdd Mon Sep 17 00:00:00 2001
From: Yuxuan Chen 
Date: Thu, 7 May 2026 20:24:49 -0700
Subject: [PATCH] [Clang] Rebind the closure temporary when rebuilding default
 member initializers

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

Since the CWG1815 implementation, `InitListChecker` rebuilds a default
member initializer at its point of use in aggregate initialization. The
rebuild uses the `EnsureImmediateInvocationInDefaultArgs` tree
transform, where `TransformCXXBindTemporaryExpr` strips
`CXXBindTemporaryExpr` nodes, relying on the subexpression's rebuild to
re-create the temporary binding: every `Rebuild*` path funnels through
`Sema::MaybeBindToTemporary`, which also re-registers the cleanup in
the current evaluation context.

However, the transform overrides `TransformLambdaExpr` to return the
closure unchanged (the body is not a subexpression), skipping the
`MaybeBindToTemporary` call that `BuildLambdaExpr` ends with. The
rebuilt initializer then lacks both the `CXXBindTemporaryExpr` around
the closure and the `ExprWithCleanups` marker, so CodeGen never emits
the closure's destructor and init-captured members leak.

Restore the side effect in the override: re-bind the closure to a
temporary, exactly as building the lambda would have. This is a no-op
for trivially destructible closures.

Co-Authored-By: Claude Fable 5 
---
 clang/lib/Sema/SemaExpr.cpp   | 12 --
 ...469-default-member-init-lambda-cleanup.cpp | 24 +++
 2 files changed, 34 insertions(+), 2 deletions(-)
 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..cf1ef0a81185f 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5710,9 +5710,17 @@ struct EnsureImmediateInvocationInDefaultArgs
 
   // 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.
+  // The body is not a subexpression, so we do not transform the lambda
+  // itself. However, the closure object is returned without rebuilding it,
+  // so we must redo the effects building a lambda has on the enclosing
+  // context: any CXXBindTemporaryExpr around it has been dropped by
+  // TransformCXXBindTemporaryExpr, and the enclosing context must be
+  // marked as requiring cleanups for the closure's destructor to be run
+  // at the end of the full-expression.
   // FIXME: Immediate calls in capture initializers should be transformed.
-  ExprResult TransformLambdaExpr(LambdaExpr *E) { return E; }
+  ExprResult TransformLambdaExpr(LambdaExpr *E) {
+return SemaRef.MaybeBindToTemporary(E);
+  }
   ExprResult TransformBlockExpr(BlockExpr *E) { return E; }
 
   // Make sure we don't rebuild the this pointer as it would
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 0..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  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


[clang] [Clang] Track temporary cleanups in rebuilt default member initializers (PR #196597)

2026-06-10 Thread Yuxuan Chen via cfe-commits

https://github.com/yuxuanchen1997 edited 
https://github.com/llvm/llvm-project/pull/196597
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang] Track temporary cleanups in rebuilt default member initializers (PR #196597)

2026-06-09 Thread Yuxuan Chen via cfe-commits

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

>From e39df0f164091095fea19c49dd48a1912fb6acf7 Mon Sep 17 00:00:00 2001
From: Yuxuan Chen 
Date: Thu, 7 May 2026 20:24:49 -0700
Subject: [PATCH] [Clang] Rebind the closure temporary when rebuilding default
 member initializers

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

Since the CWG1815 implementation, InitListChecker rebuilds a default
member initializer at its point of use in aggregate initialization. The
rebuild uses the EnsureImmediateInvocationInDefaultArgs tree transform,
where TransformCXXBindTemporaryExpr strips CXXBindTemporaryExpr nodes,
relying on the subexpression's rebuild to re-create the temporary
binding: every Rebuild path funnels through Sema::MaybeBindToTemporary,
which also re-registers the cleanup in the current evaluation context.

However, the transform overrides TransformLambdaExpr to return the
closure unchanged (the body is not a subexpression), skipping the
MaybeBindToTemporary call that BuildLambdaExpr ends with. The rebuilt
initializer then lacks both the CXXBindTemporaryExpr around the closure
and the ExprWithCleanups marker, so CodeGen never emits the closure's
destructor and init-captured members leak.

Restore the side effect in the override: re-bind the closure to a
temporary, exactly as building the lambda would have. This is a no-op
for trivially destructible closures.

Co-Authored-By: Claude Fable 5 
---
 clang/lib/Sema/SemaExpr.cpp   | 12 --
 ...469-default-member-init-lambda-cleanup.cpp | 24 +++
 2 files changed, 34 insertions(+), 2 deletions(-)
 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..cf1ef0a81185f 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5710,9 +5710,17 @@ struct EnsureImmediateInvocationInDefaultArgs
 
   // 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.
+  // The body is not a subexpression, so we do not transform the lambda
+  // itself. However, the closure object is returned without rebuilding it,
+  // so we must redo the effects building a lambda has on the enclosing
+  // context: any CXXBindTemporaryExpr around it has been dropped by
+  // TransformCXXBindTemporaryExpr, and the enclosing context must be
+  // marked as requiring cleanups for the closure's destructor to be run
+  // at the end of the full-expression.
   // FIXME: Immediate calls in capture initializers should be transformed.
-  ExprResult TransformLambdaExpr(LambdaExpr *E) { return E; }
+  ExprResult TransformLambdaExpr(LambdaExpr *E) {
+return SemaRef.MaybeBindToTemporary(E);
+  }
   ExprResult TransformBlockExpr(BlockExpr *E) { return E; }
 
   // Make sure we don't rebuild the this pointer as it would
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 0..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  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


[clang] [Clang] Track temporary cleanups in rebuilt default member initializers (PR #196597)

2026-05-29 Thread Yuxuan Chen via cfe-commits

https://github.com/yuxuanchen1997 edited 
https://github.com/llvm/llvm-project/pull/196597
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang] Track temporary cleanups in rebuilt default member initializers (PR #196597)

2026-05-09 Thread Yuxuan Chen via cfe-commits

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 
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 {
+  using Base = TreeTransform;
+
   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(UnwrappedInit))
+  UnwrappedInit = FE->getSubExpr();
+
+if (auto *MTE =
+dyn_cast_if_present(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(UnwrappedInit))
+  return TransformCX

[clang] [Clang] Track temporary cleanups in rebuilt default member initializers (PR #196597)

2026-05-09 Thread Eli Friedman via cfe-commits

https://github.com/efriedma-quic commented:

I wrote up some thoughts on issues in this area in 
https://github.com/llvm/llvm-project/issues/85601#issuecomment-3100319152 .

I think we need to more fundamentally rethink how we do this. I think we need 
to more explicitly fork the codepaths so the various scopes and context is 
correct.  Trying to explicitly fixup various subexpressions will lead to a 
bunch of subtle bugs.  In particular, I think we shouldn't 
EnterExpressionEvaluationContext in the aggregate case.

https://github.com/llvm/llvm-project/pull/196597
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang] Track temporary cleanups in rebuilt default member initializers (PR #196597)

2026-05-09 Thread via cfe-commits

llvmorg-github-actions[bot] wrote:




@llvm/pr-subscribers-clang

Author: Yuxuan Chen (yuxuanchen1997)


Changes

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

---
Full diff: https://github.com/llvm/llvm-project/pull/196597.diff


3 Files Affected:

- (modified) clang/lib/Sema/SemaExpr.cpp (+54-1) 
- (modified) clang/lib/Sema/SemaExprCXX.cpp (+9) 
- (added) clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp 
(+24) 


``diff
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 {
+  using Base = TreeTransform;
+
   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(UnwrappedInit))
+  UnwrappedInit = FE->getSubExpr();
+
+if (auto *MTE =
+dyn_cast_if_present(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(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(SourceLoca

[clang] [Clang] Track temporary cleanups in rebuilt default member initializers (PR #196597)

2026-05-09 Thread Eli Friedman via cfe-commits


@@ -6730,7 +6731,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));

efriedma-quic wrote:

Please don't do this.

We already have infrastructure for delayed emission of inline functions; if 
that isn't working correctly, please fix it instead of trying to side-step it.  
(Maybe that means we need to hook up delayed emission for destructors where we 
currently don't have a body, but might synthesize one later?)

https://github.com/llvm/llvm-project/pull/196597
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang] Track temporary cleanups in rebuilt default member initializers (PR #196597)

2026-05-09 Thread Yuxuan Chen via cfe-commits

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

>From 0e05f65a7f832d6dc76a839de985a9b41329e076 Mon Sep 17 00:00:00 2001
From: Yuxuan Chen 
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 98062afae4577..e75f2843fdf8f 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5703,11 +5703,54 @@ struct ImmediateCallVisitor : 
DynamicRecursiveASTVisitor {
 
 struct EnsureImmediateInvocationInDefaultArgs
 : TreeTransform {
+  using Base = TreeTransform;
+
   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(UnwrappedInit))
+  UnwrappedInit = FE->getSubExpr();
+
+if (auto *MTE =
+dyn_cast_if_present(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(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) {

[clang] [Clang] Track temporary cleanups in rebuilt default member initializers (PR #196597)

2026-05-09 Thread Eli Friedman via cfe-commits

https://github.com/efriedma-quic edited 
https://github.com/llvm/llvm-project/pull/196597
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang] Track temporary cleanups in rebuilt default member initializers (PR #196597)

2026-05-09 Thread Yuxuan Chen via cfe-commits

https://github.com/yuxuanchen1997 edited 
https://github.com/llvm/llvm-project/pull/196597
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits