https://github.com/Xazax-hun updated https://github.com/llvm/llvm-project/pull/206375
From 7c64adbb6632caff6c8a532c4268588bd72ff9e6 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 | 5 ++ clang/lib/Sema/AnalysisBasedWarnings.cpp | 77 +++++++++++++++---- clang/lib/Sema/SemaDeclCXX.cpp | 8 ++ .../dangling-field-implicit-ctor.cpp | 49 ++++++++++++ 4 files changed, 125 insertions(+), 14 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..5d82d31970d86 100644 --- a/clang/include/clang/Sema/AnalysisBasedWarnings.h +++ b/clang/include/clang/Sema/AnalysisBasedWarnings.h @@ -117,6 +117,11 @@ class AnalysisBasedWarnings { // Issue warnings that require whole-translation-unit analysis. void IssueWarnings(TranslationUnitDecl *D); + // Run analysis-based warnings on an implicitly-defined function body (e.g. a + // defaulted/implicit default constructor). Such functions never reach the + // normal IssueWarnings path. + void IssueWarningsForImplicitFunction(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..d7ea18f7bbe88 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2906,6 +2906,28 @@ 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(); +} + +// Returns true when analysis-based warnings should be skipped for D: warnings +// are ignored, D is in a suppressed system header, or D is in a dependent +// context (which is handled later at instantiation time). +static bool shouldSkipAnalysisForDecl(Sema &S, const Decl *D) { + DiagnosticsEngine &Diags = S.getDiagnostics(); + if (Diags.getIgnoreAllWarnings() || + (Diags.getSuppressSystemWarnings() && + S.SourceMgr.isInSystemHeader(D->getLocation()))) + return true; + return cast<DeclContext>(D)->isDependentContext(); +} + static void LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU, clang::lifetimes::LifetimeSafetyStats &LSStats) { @@ -2922,12 +2944,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 +3022,45 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( LifetimeSafetyTUAnalysis(S, TU, LSStats); } +void clang::sema::AnalysisBasedWarnings::IssueWarningsForImplicitFunction( + const Decl *D) { + // Currently this runs only lifetime safety: a default member initializer + // applied by a synthesized constructor can bind a view/pointer member to a + // temporary that dies at the end of construction -- a dangling field that + // would otherwise be missed. + if (!D || !D->getBody()) + return; + // In TU-end mode IsLifetimeSafetyEnabled returns false for non-TU decls, so + // such definitions are reached only via the call-graph walk, not here. + if (!lifetimes::IsLifetimeSafetyEnabled(S, D)) + return; + if (shouldSkipAnalysisForDecl(S, D) || 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) { @@ -3017,14 +3073,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( // time. DiagnosticsEngine &Diags = S.getDiagnostics(); - // Do not do any analysis if we are going to just ignore them. - if (Diags.getIgnoreAllWarnings() || - (Diags.getSuppressSystemWarnings() && - S.SourceMgr.isInSystemHeader(D->getLocation()))) - return; - - // For code in dependent contexts, we'll do this at instantiation time. - if (cast<DeclContext>(D)->isDependentContext()) + if (shouldSkipAnalysisForDecl(S, D)) return; if (S.hasUncompilableErrorOccurred()) { diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index ffce0a146865e..c645d96da5c00 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.IssueWarningsForImplicitFunction(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.IssueWarningsForImplicitFunction(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
