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 <[email protected]> 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<EnsureImmediateInvocationInDefaultArgs> { + using Base = TreeTransform<EnsureImmediateInvocationInDefaultArgs>; + 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<FullExpr>(UnwrappedInit)) + UnwrappedInit = FE->getSubExpr(); + + if (auto *MTE = + dyn_cast_if_present<MaterializeTemporaryExpr>(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<CXXBindTemporaryExpr>(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(SourceLocation Loc, FieldDecl *Field) { runWithSufficientStackSpace(Loc, [&] { MarkDeclarationsReferencedInExpr(E, /*SkipLocalVariables=*/false); }); - if (isInLifetimeExtendingContext()) + if (isInLifetimeExtendingContext()) { DiscardCleanupsInEvaluationContext(); + if (RebuiltInitNeedsCleanups) + Cleanup.setExprNeedsCleanups(true); + } // C++11 [class.base.init]p7: // The initialization of each base and member constitutes a // full-expression. diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index e60d5d7748b44..6022be02aee1d 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -13,6 +13,7 @@ #include "TreeTransform.h" #include "TypeLocBuilder.h" +#include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTLambda.h" #include "clang/AST/CXXInheritance.h" @@ -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)); CheckDestructorAccess(E->getExprLoc(), Destructor, PDiag(diag::err_access_dtor_temp) << E->getType()); 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
