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
