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

Reply via email to