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

>From 7347f4ca27f38c2b3320b4d6bc0481f3f3058319 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 capture initializers 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 recreate the temporary binding and re-register 
cleanups in the current evaluation context.

However, the transform overrides TransformLambdaExpr because the lambda body is 
not a subexpression. Returning the original lambda unchanged skips the 
MaybeBindToTemporary call that BuildLambdaExpr would normally perform, so the 
rebuilt initializer can lack the closure temporary binding and cleanup marker. 
CodeGen then misses the closure destructor and init-captured members can leak.

Lambda init-capture initializers are evaluated in the enclosing context and can 
also contain immediate invocations or source-location expressions that need to 
be rebuilt at the default-initializer use site. Rebuild those initializer 
expressions without rebuilding the closure type, capture declarations, or body, 
so body references to init-capture declarations remain valid. When a capture 
initializer changes, create a replacement LambdaExpr that shares the existing 
closure and body, then bind the lambda temporary explicitly to restore cleanup 
emission.

Co-Authored-By: GPT-5.5 <[email protected]>
---
 clang/docs/ReleaseNotes.rst                   |  2 +
 clang/lib/Sema/SemaExpr.cpp                   | 56 ++++++++++++++++---
 ...469-default-member-init-lambda-cleanup.cpp | 37 ++++++++++++
 ...469-default-member-init-lambda-capture.cpp | 23 ++++++++
 clang/test/SemaCXX/source_location.cpp        | 12 ----
 5 files changed, 110 insertions(+), 20 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/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 8bb17755b28f5..f2325c11eaeba 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -810,6 +810,8 @@ Bug Fixes to C++ Support
 - Fixed a crash in constant evaluation using placement new on an array which 
was later initialized. (#GH196450)
 - Fixed an issue where Clang incorrectly accepted invalid unqualified uses of 
local nested class names outside their declaring scope. (#GH184622)
 - Fixed a crash when parsing invalid friend declaration with storage-class 
specifier. (#GH186569)
+- Fixed missing destructor cleanups for lambda init-captures in default member
+  initializers used during aggregate initialization. (#GH196469)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 7c868d176e803..25ff387f23441 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,51 @@ 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 bodies are not subexpressions of the enclosing default initializer,
+  // but init-capture expressions are evaluated in the enclosing context. Keep
+  // the existing closure type and capture declarations so the existing body
+  // still refers to the right declarations.
+  ExprResult TransformLambdaExpr(LambdaExpr *E) {
+    SmallVector<Expr *, 4> CaptureInits(E->capture_inits());
+
+    bool Changed = false;
+    for (unsigned I = 0, N = E->capture_size(); I != N; ++I) {
+      const LambdaCapture *C = E->capture_begin() + I;
+      if (!E->isInitCapture(C))
+        continue;
+
+      auto *VD = cast<VarDecl>(C->getCapturedVar());
+      Expr *Init = CaptureInits[I];
+      ExprResult NewInit =
+          TransformInitializer(Init, VD->getInitStyle() == VarDecl::CallInit);
+      if (NewInit.isInvalid())
+        return ExprError();
+      Changed |= NewInit.get() != Init;
+      CaptureInits[I] = NewInit.get();
+    }
 
-  // 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; }
+    LambdaExpr *Lambda = E;
+    if (Changed) {
+      // Reuse the existing closure class: it owns the capture declarations,
+      // fields, and call operator body. Only the LambdaExpr's capture
+      // initializer list is replaced.
+      Lambda = LambdaExpr::Create(
+          SemaRef.Context, E->getLambdaClass(), E->getIntroducerRange(),
+          E->getCaptureDefault(), E->getCaptureDefaultLoc(),
+          E->hasExplicitParameters(), E->hasExplicitResultType(), CaptureInits,
+          E->getEndLoc(), E->containsUnexpandedParameterPack());
+    }
+
+    return SemaRef.MaybeBindToTemporary(Lambda);
+  }
   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..905db0f583afd
--- /dev/null
+++ b/clang/test/CodeGenCXX/gh196469-default-member-init-lambda-cleanup.cpp
@@ -0,0 +1,37 @@
+// 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{};
+
+int side();
+
+struct ReturnsCapture {
+  int x;
+  int value = [value = x] { return value; }();
+};
+
+ReturnsCapture kReturnsCapture{side()};
+
+// CHECK-LABEL: define internal void @__cxx_global_var_init
+// CHECK: call void @_ZN5NoisyC1Ev
+// CHECK: call void @_ZN8FunctionC1IN7Options8functionMUlvE_EEET_
+// CHECK: call void @_ZN7Options8functionMUlvE_D1Ev
+// CHECK: call {{.*}} @_ZNK14ReturnsCapture5valueMUlvE_clEv
+
+// CHECK-LABEL: define linkonce_odr {{.*}} 
@_ZNK14ReturnsCapture5valueMUlvE_clEv
+// CHECK: ret i32
+
+// 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()};
diff --git a/clang/test/SemaCXX/source_location.cpp 
b/clang/test/SemaCXX/source_location.cpp
index eaa6cb04c5d1c..1ede22eaf458a 100644
--- a/clang/test/SemaCXX/source_location.cpp
+++ b/clang/test/SemaCXX/source_location.cpp
@@ -862,23 +862,11 @@ struct CompoundLiteral {
 static_assert(CompoundLiteral{}.a == __LINE__);
 
 
-// FIXME
-// Init captures are subexpressions of the lambda expression
-// so according to the standard immediate invocations in init captures
-// should be evaluated at the call site.
-// However Clang does not yet implement this as it would introduce
-// a fair bit of complexity.
-// We intend to implement that functionality once we find real world
-// use cases that require it.
 constexpr int test_init_capture(int a =
                 [b = SL::current().line()] { return b; }()) {
   return a;
 }
-#if defined(USE_CONSTEVAL) && !defined(NEW_INTERP)
-static_assert(test_init_capture() == __LINE__ - 4);
-#else
 static_assert(test_init_capture() == __LINE__ );
-#endif
 
 namespace check_immediate_invocations_in_templates {
 

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

Reply via email to