https://github.com/Xazax-hun created https://github.com/llvm/llvm-project/pull/206375
A default member initializer can bind a view/pointer member to a temporary that dies at the end of construction. This dangling field was caught for user-written constructors but silently missed for implicit, defaulted, and inheriting constructors, whose synthesized bodies never reach IssueWarnings. Add IssueLifetimeSafetyWarningsForImplicitFunction, called from DefineImplicitDefaultConstructor and DefineInheritingConstructor, to run the lifetime safety analysis on these synthesized bodies. Extract the shared CFG build options into setLifetimeSafetyCFGBuildOptions, reused by the TU-end analysis. Assisted-by: Claude Opus 4.8 From 8030be1fd1e72d7959cda4c68d2d5e31945248ae Mon Sep 17 00:00:00 2001 From: Gabor Horvath <[email protected]> Date: Sun, 28 Jun 2026 22:21:05 +0100 Subject: [PATCH] [LifetimeSafety] Analyze synthesized constructors for dangling NSDMIs A default member initializer can bind a view/pointer member to a temporary that dies at the end of construction. This dangling field was caught for user-written constructors but silently missed for implicit, defaulted, and inheriting constructors, whose synthesized bodies never reach IssueWarnings. Add IssueLifetimeSafetyWarningsForImplicitFunction, called from DefineImplicitDefaultConstructor and DefineInheritingConstructor, to run the lifetime safety analysis on these synthesized bodies. Extract the shared CFG build options into setLifetimeSafetyCFGBuildOptions, reused by the TU-end analysis. Assisted-by: Claude Opus 4.8 --- .../clang/Sema/AnalysisBasedWarnings.h | 7 +++ clang/lib/Sema/AnalysisBasedWarnings.cpp | 62 +++++++++++++++++-- clang/lib/Sema/SemaDeclCXX.cpp | 8 +++ .../dangling-field-implicit-ctor.cpp | 49 +++++++++++++++ 4 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 clang/test/Sema/LifetimeSafety/dangling-field-implicit-ctor.cpp diff --git a/clang/include/clang/Sema/AnalysisBasedWarnings.h b/clang/include/clang/Sema/AnalysisBasedWarnings.h index 0ed61e56825be..b46befd80154c 100644 --- a/clang/include/clang/Sema/AnalysisBasedWarnings.h +++ b/clang/include/clang/Sema/AnalysisBasedWarnings.h @@ -117,6 +117,13 @@ class AnalysisBasedWarnings { // Issue warnings that require whole-translation-unit analysis. void IssueWarnings(TranslationUnitDecl *D); + // Run the lifetime safety analysis on a synthesized constructor body (e.g. a + // defaulted/implicit default constructor). Such functions never reach + // IssueWarnings, yet a default member initializer can bind a view/pointer + // member to a temporary that dies at the end of construction -- a dangling + // field the analysis would otherwise miss. + void IssueLifetimeSafetyWarningsForImplicitFunction(const Decl *D); + void registerVarDeclWarning(VarDecl *VD, PossiblyUnreachableDiag PUD); void issueWarningsForRegisteredVarDecl(VarDecl *VD); diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index e070d9f1a9b85..c8425ab6091b0 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2906,6 +2906,16 @@ class CallableVisitor : public DynamicRecursiveASTVisitor { } }; +// CFG build options for running the lifetime safety analysis on a function. +static void setLifetimeSafetyCFGBuildOptions(AnalysisDeclContext &AC) { + AC.getCFGBuildOptions().PruneTriviallyFalseEdges = true; + AC.getCFGBuildOptions().AddLifetime = true; + AC.getCFGBuildOptions().AddParameterLifetimes = true; + AC.getCFGBuildOptions().AddInitializers = true; + AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true; + AC.getCFGBuildOptions().setAllAlwaysAdd(); +} + static void LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU, clang::lifetimes::LifetimeSafetyStats &LSStats) { @@ -2922,12 +2932,7 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU, if (!FD) continue; AnalysisDeclContext AC(nullptr, FD); - AC.getCFGBuildOptions().PruneTriviallyFalseEdges = true; - AC.getCFGBuildOptions().AddLifetime = true; - AC.getCFGBuildOptions().AddParameterLifetimes = true; - AC.getCFGBuildOptions().AddInitializers = true; - AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true; - AC.getCFGBuildOptions().setAllAlwaysAdd(); + setLifetimeSafetyCFGBuildOptions(AC); if (AC.getCFG()) runLifetimeSafetyAnalysis(AC, &SemaHelper, lifetimes::GetLifetimeSafetyOpts(S, FD), @@ -3005,6 +3010,51 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( LifetimeSafetyTUAnalysis(S, TU, LSStats); } +void clang::sema::AnalysisBasedWarnings:: + IssueLifetimeSafetyWarningsForImplicitFunction(const Decl *D) { + if (!D || !D->getBody()) + return; + // TU-end analysis reaches such definitions via its own call-graph walk; only + // handle the per-function mode here. The remaining guards mirror + // IssueWarnings. + if (S.getLangOpts().EnableLifetimeSafetyTUAnalysis || + !S.getLangOpts().CPlusPlus || !lifetimes::IsLifetimeSafetyEnabled(S, D)) + return; + + DiagnosticsEngine &Diags = S.getDiagnostics(); + if (Diags.getIgnoreAllWarnings() || + (Diags.getSuppressSystemWarnings() && + S.SourceMgr.isInSystemHeader(D->getLocation()))) + return; + if (cast<DeclContext>(D)->isDependentContext()) + return; + if (S.hasUncompilableErrorOccurred()) + return; + + // A synthesized constructor can only dangle a field through an NSDMI, so skip + // classes with no in-class field initializer. We do not narrow by member + // type: an NSDMI can bind a borrow nested inside an aggregate member too. + if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(D)) { + bool HasInClassInit = false; + for (const FieldDecl *FD : Ctor->getParent()->fields()) + if (FD->hasInClassInitializer()) { + HasInClassInit = true; + break; + } + if (!HasInClassInit) + return; + } + + AnalysisDeclContext AC(/*AnalysisDeclContextManager=*/nullptr, D); + setLifetimeSafetyCFGBuildOptions(AC); + + lifetimes::LifetimeSafetySemaHelperImpl SemaHelper(S); + if (AC.getCFG()) + lifetimes::runLifetimeSafetyAnalysis(AC, &SemaHelper, + lifetimes::GetLifetimeSafetyOpts(S, D), + LSStats, S.CollectStats); +} + void clang::sema::AnalysisBasedWarnings::IssueWarnings( sema::AnalysisBasedWarnings::Policy P, sema::FunctionScopeInfo *fscope, const Decl *D, QualType BlockType) { diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index ffce0a146865e..04d8328120932 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -14407,6 +14407,10 @@ void Sema::DefineImplicitDefaultConstructor(SourceLocation CurrentLocation, } DiagnoseUninitializedFields(*this, Constructor); + + // The synthesized body applies the class's NSDMIs and never reaches the + // normal IssueWarnings path, so run lifetime safety on it here. + AnalysisWarnings.IssueLifetimeSafetyWarningsForImplicitFunction(Constructor); } void Sema::ActOnFinishDelayedMemberInitializers(Decl *D) { @@ -14588,6 +14592,10 @@ void Sema::DefineInheritingConstructor(SourceLocation CurrentLocation, } DiagnoseUninitializedFields(*this, Constructor); + + // The synthesized body applies the class's NSDMIs and never reaches the + // normal IssueWarnings path, so run lifetime safety on it here. + AnalysisWarnings.IssueLifetimeSafetyWarningsForImplicitFunction(Constructor); } CXXDestructorDecl *Sema::DeclareImplicitDestructor(CXXRecordDecl *ClassDecl) { diff --git a/clang/test/Sema/LifetimeSafety/dangling-field-implicit-ctor.cpp b/clang/test/Sema/LifetimeSafety/dangling-field-implicit-ctor.cpp new file mode 100644 index 0000000000000..e3ea0f583f06f --- /dev/null +++ b/clang/test/Sema/LifetimeSafety/dangling-field-implicit-ctor.cpp @@ -0,0 +1,49 @@ +// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-dangling-field -Wno-dangling-gsl -verify=expected,perfunc %s +// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-dangling-field -Wno-dangling-gsl -verify=expected %s + +#include "Inputs/lifetime-analysis.h" + +std::string make(); + +// A default member initializer can bind a view member to a temporary that dies +// at the end of construction. This must be caught even when the constructor that +// applies the NSDMI is implicit, defaulted, or inheriting -- such synthesized +// bodies never reach the normal warning path. + +struct ImplicitCtor { + std::string_view v = make(); // expected-warning {{stack memory associated with temporary object escapes to the field 'v' which will dangle}} + // expected-note@-1 {{this field dangles}} +}; +void use_implicit() { ImplicitCtor c; (void)c; } // perfunc-note {{in implicit default constructor for 'ImplicitCtor' first required here}} + +struct DefaultedCtor { + std::string_view v = make(); // expected-warning {{stack memory associated with temporary object escapes to the field 'v' which will dangle}} + // expected-note@-1 {{this field dangles}} + DefaultedCtor() = default; +}; +void use_defaulted() { DefaultedCtor c; (void)c; } // perfunc-note {{in defaulted default constructor for 'DefaultedCtor' first required here}} + +struct UserCtor { + std::string_view v = make(); // expected-warning {{stack memory associated with temporary object escapes to the field 'v' which will dangle}} + // expected-note@-1 {{this field dangles}} + UserCtor() {} +}; + +struct Base { + Base(int); +}; +// An inheriting constructor applies the derived class's NSDMIs. +struct Inheriting : Base { + using Base::Base; + std::string_view v = make(); // expected-warning {{stack memory associated with temporary object escapes to the field 'v' which will dangle}} + // expected-note@-1 {{this field dangles}} +}; +// No "first required here" note here: inheriting-constructor synthesis does not +// surface an instantiation context for the diagnostic. +void use_inheriting() { Inheriting i(0); (void)i; } + +// A string literal has static storage, so binding a view to it does not dangle. +struct SafeLiteral { + std::string_view v = "literal"; +}; +void use_safe() { SafeLiteral c; (void)c; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
