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

>From 5f5e8ce1124165cfb9755b6603c52fc144fd7e0c Mon Sep 17 00:00:00 2001
From: Yuxuan Chen <[email protected]>
Date: Thu, 7 May 2026 20:24:49 -0700
Subject: [PATCH] [Clang] Rebuild lambda captures in 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 because the lambda body is not a subexpression. That
skips 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.

Lambda init-captures are different from the body: their initializers are
evaluated in the enclosing context and can also contain immediate
invocations that need to be rebuilt at the default-initializer use site.
Visit init-captures when deciding whether the default initializer needs
rebuilding, and rebuild the lambda shell/captures while using
`SkipLambdaBody` to leave the body itself untouched. Rebuilding the
lambda this way also restores the closure temporary binding and cleanup
through `BuildLambdaExpr`.

Co-Authored-By: GPT-5.5 <[email protected]>
---
 clang/lib/Sema/SemaExpr.cpp                   | 27 +++++++++++++------
 ...469-default-member-init-lambda-cleanup.cpp | 24 +++++++++++++++++
 ...469-default-member-init-lambda-capture.cpp | 23 ++++++++++++++++
 3 files changed, 66 insertions(+), 8 deletions(-)
 create mode 100644 
clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp
 create mode 100644 
clang/test/SemaCXX/gh196469-default-member-init-lambda-capture.cpp

diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 7c868d176e803..57a7beadc9b7c 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5703,12 +5703,17 @@ struct ImmediateCallVisitor : 
DynamicRecursiveASTVisitor {
   }
 
   // A nested lambda might have parameters with immediate invocations
-  // in their default arguments.
+  // in their default arguments, or init-captures that are evaluated in the
+  // enclosing context.
   // The compound statement is not visited (as it does not constitute a
   // subexpression).
-  // FIXME: We should consider visiting and transforming captures
-  // with init expressions.
   bool VisitLambdaExpr(LambdaExpr *E) override {
+    auto Init = E->capture_init_begin();
+    for (auto C = E->capture_begin(), CEnd = E->capture_end(); C != CEnd;
+         ++C, ++Init) {
+      if (E->isInitCapture(C) && !TraverseLambdaCapture(E, C, *Init))
+        return false;
+    }
     return VisitCXXMethodDecl(E->getCallOperator());
   }
 
@@ -5723,16 +5728,22 @@ struct ImmediateCallVisitor : 
DynamicRecursiveASTVisitor {
 
 struct EnsureImmediateInvocationInDefaultArgs
     : TreeTransform<EnsureImmediateInvocationInDefaultArgs> {
+  using Base = TreeTransform<EnsureImmediateInvocationInDefaultArgs>;
+
   EnsureImmediateInvocationInDefaultArgs(Sema &SemaRef)
       : TreeTransform(SemaRef) {}
 
   bool AlwaysRebuild() { return true; }
+  bool ReplacingOriginal() { return true; }
 
-  // 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.
-  // FIXME: Immediate calls in capture initializers should be transformed.
-  ExprResult TransformLambdaExpr(LambdaExpr *E) { return E; }
+  // Lambda bodies are not subexpressions of the enclosing default initializer,
+  // but init-capture expressions are evaluated in the enclosing context.
+  ExprResult TransformLambdaExpr(LambdaExpr *E) {
+    return Base::TransformLambdaExpr(E);
+  }
+  StmtResult TransformLambdaBody(LambdaExpr *E, Stmt *Body) {
+    return Base::SkipLambdaBody(E, Body);
+  }
   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 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
diff --git a/clang/test/SemaCXX/gh196469-default-member-init-lambda-capture.cpp 
b/clang/test/SemaCXX/gh196469-default-member-init-lambda-capture.cpp
new file mode 100644
index 0000000000000..358e06f861bf8
--- /dev/null
+++ b/clang/test/SemaCXX/gh196469-default-member-init-lambda-capture.cpp
@@ -0,0 +1,23 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+
+struct Noisy {
+  int x;
+  consteval Noisy(int x) : x(x) {}
+  ~Noisy() {}
+};
+
+struct Function {
+  template <typename F> Function(F) {}
+};
+
+struct Options {
+  int x;
+  Function function{ // expected-note {{declared here}}
+      // expected-error@+2 {{call to consteval function}}
+      // expected-note@+1 {{implicit use of 'this' pointer is only allowed 
within the evaluation of a call to a 'constexpr' member function}}
+      [noisy = Noisy{x}] {}};
+};
+
+int foo();
+// expected-note@+1 {{in the default initializer of 'function'}}
+Options options{foo()};

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

Reply via email to