[clang] 63d4b48 - [LifetimeSafety] Analyze synthesized constructors for dangling NSDMIs (#206375)

via cfe-commits cfe-commits at lists.llvm.org
Mon Jun 29 03:39:33 PDT 2026


Author: Gábor Horváth
Date: 2026-06-29T11:39:27+01:00
New Revision: 63d4b487846d2bcd044636bdf905f291d5daf450

URL: https://github.com/llvm/llvm-project/commit/63d4b487846d2bcd044636bdf905f291d5daf450
DIFF: https://github.com/llvm/llvm-project/commit/63d4b487846d2bcd044636bdf905f291d5daf450.diff

LOG: [LifetimeSafety] Analyze synthesized constructors for dangling NSDMIs (#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

Added: 
    clang/test/Sema/LifetimeSafety/dangling-field-implicit-ctor.cpp

Modified: 
    clang/include/clang/Sema/AnalysisBasedWarnings.h
    clang/lib/Sema/AnalysisBasedWarnings.cpp
    clang/lib/Sema/SemaDeclCXX.cpp

Removed: 
    


################################################################################
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 at -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 at -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 at -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 at -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; }


        


More information about the cfe-commits mailing list