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
