Author: Utkarsh Saxena
Date: 2026-04-13T20:36:56+05:30
New Revision: 386e67da649161349cea2fba7401c0829f248bc1

URL: 
https://github.com/llvm/llvm-project/commit/386e67da649161349cea2fba7401c0829f248bc1
DIFF: 
https://github.com/llvm/llvm-project/commit/386e67da649161349cea2fba7401c0829f248bc1.diff

LOG: [LifetimeSafety] Suggest/infer annotation in constructors (#191699)

This change improves the lifetime safety checker to detect when
constructor parameters escape to class fields and suggest appropriate
`[[clang::lifetimebound]]` annotations.

```cpp
struct A {
  View v;
  A(const MyObj& obj) : v(obj) {} // Now suggests [[clang::lifetimebound]]
};
```

Added: 
    

Modified: 
    clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
    clang/lib/Analysis/LifetimeSafety/Checker.cpp
    clang/lib/Sema/AnalysisBasedWarnings.cpp
    clang/lib/Sema/SemaLifetimeSafety.h
    clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
    clang/test/Sema/warn-lifetime-safety-suggestions.cpp
    clang/test/Sema/warn-lifetime-safety.cpp

Removed: 
    


################################################################################
diff  --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 08038dd096685..83c3c455c4c81 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -28,6 +28,7 @@
 #include "clang/Analysis/Analyses/LifetimeSafety/MovedLoans.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
 #include "clang/Analysis/AnalysisDeclContext.h"
+#include "llvm/ADT/PointerUnion.h"
 #include <cstddef>
 #include <memory>
 
@@ -88,10 +89,13 @@ class LifetimeSafetySemaHelper {
                                           const Expr *UseExpr,
                                           const Expr *InvalidationExpr) {}
 
-  // Suggests lifetime bound annotations for function paramters.
+  using EscapingTarget =
+      llvm::PointerUnion<const Expr *, const FieldDecl *, const VarDecl *>;
+
+  // Suggests lifetime bound annotations for function parameters.
   virtual void suggestLifetimeboundToParmVar(SuggestionScope Scope,
                                              const ParmVarDecl *ParmToAnnotate,
-                                             const Expr *EscapeExpr) {}
+                                             EscapingTarget Target) {}
 
   // Reports misuse of [[clang::noescape]] when parameter escapes through 
return
   virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,

diff  --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 36477c6f67b52..fe103739dbcb0 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -53,13 +53,12 @@ struct PendingWarning {
 
 using AnnotationTarget =
     llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>;
-using EscapingTarget =
-    llvm::PointerUnion<const Expr *, const FieldDecl *, const VarDecl *>;
+using EscapingTarget = LifetimeSafetySemaHelper::EscapingTarget;
 
 class LifetimeChecker {
 private:
   llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
-  llvm::DenseMap<AnnotationTarget, const Expr *> AnnotationWarningsMap;
+  llvm::DenseMap<AnnotationTarget, EscapingTarget> AnnotationWarningsMap;
   llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap;
   const LoanPropagationAnalysis &LoanPropagation;
   const MovedLoansAnalysis &MovedLoans;
@@ -67,6 +66,7 @@ class LifetimeChecker {
   FactManager &FactMgr;
   LifetimeSafetySemaHelper *SemaHelper;
   ASTContext &AST;
+  const Decl *FD;
 
   static SourceLocation
   GetFactLoc(llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> F) 
{
@@ -89,7 +89,7 @@ class LifetimeChecker {
                   LifetimeSafetySemaHelper *SemaHelper)
       : LoanPropagation(LoanPropagation), MovedLoans(MovedLoans),
         LiveOrigins(LiveOrigins), FactMgr(FM), SemaHelper(SemaHelper),
-        AST(ADC.getASTContext()) {
+        AST(ADC.getASTContext()), FD(ADC.getDecl()) {
     for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
       for (const Fact *F : FactMgr.getFacts(B))
         if (const auto *EF = F->getAs<ExpireFact>())
@@ -125,10 +125,15 @@ class LifetimeChecker {
           NoescapeWarningsMap.try_emplace(PVD, GlobalEsc->getGlobal());
         return;
       }
-      // Suggest lifetimebound for parameter escaping through return.
-      if (!PVD->hasAttr<LifetimeBoundAttr>())
+      // Suggest lifetimebound for parameter escaping through return or a field
+      // in constructor.
+      if (!PVD->hasAttr<LifetimeBoundAttr>()) {
         if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
           AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
+        else if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF);
+                 FieldEsc && isa<CXXConstructorDecl>(FD))
+          AnnotationWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
+      }
       // TODO: Suggest lifetime_capture_by(this) for parameter escaping to a
       // field!
     };
@@ -292,14 +297,18 @@ class LifetimeChecker {
   static void suggestWithScopeForParmVar(LifetimeSafetySemaHelper *SemaHelper,
                                          const ParmVarDecl *PVD,
                                          SourceManager &SM,
-                                         const Expr *EscapeExpr) {
+                                         EscapingTarget EscapeTarget) {
+    if (llvm::isa<const VarDecl *>(EscapeTarget))
+      return;
+
     if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM))
       SemaHelper->suggestLifetimeboundToParmVar(
           SuggestionScope::CrossTU,
-          CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()), EscapeExpr);
+          CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()),
+          EscapeTarget);
     else
       SemaHelper->suggestLifetimeboundToParmVar(SuggestionScope::IntraTU, PVD,
-                                                EscapeExpr);
+                                                EscapeTarget);
   }
 
   static void
@@ -319,11 +328,15 @@ class LifetimeChecker {
     if (!SemaHelper)
       return;
     SourceManager &SM = AST.getSourceManager();
-    for (auto [Target, EscapeExpr] : AnnotationWarningsMap) {
+    for (auto [Target, EscapeTarget] : AnnotationWarningsMap) {
       if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>())
-        suggestWithScopeForParmVar(SemaHelper, PVD, SM, EscapeExpr);
-      else if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>())
-        suggestWithScopeForImplicitThis(SemaHelper, MD, SM, EscapeExpr);
+        suggestWithScopeForParmVar(SemaHelper, PVD, SM, EscapeTarget);
+      else if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) {
+        if (const auto *EscapeExpr = EscapeTarget.dyn_cast<const Expr *>())
+          suggestWithScopeForImplicitThis(SemaHelper, MD, SM, EscapeExpr);
+        else
+          llvm_unreachable("Implicit this can only escape via Expr (return)");
+      }
     }
   }
 
@@ -341,7 +354,7 @@ class LifetimeChecker {
   }
 
   void inferAnnotations() {
-    for (auto [Target, EscapeExpr] : AnnotationWarningsMap) {
+    for (auto [Target, EscapeTarget] : AnnotationWarningsMap) {
       if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) {
         if (!implicitObjectParamIsLifetimeBound(MD))
           SemaHelper->addLifetimeBoundToImplicitThis(cast<CXXMethodDecl>(MD));

diff  --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 48957caf61e5a..2b30aad880a27 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2925,6 +2925,8 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU,
     AC.getCFGBuildOptions().PruneTriviallyFalseEdges = true;
     AC.getCFGBuildOptions().AddLifetime = true;
     AC.getCFGBuildOptions().AddParameterLifetimes = true;
+    AC.getCFGBuildOptions().AddInitializers = true;
+    AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true;
     AC.getCFGBuildOptions().setAllAlwaysAdd();
     if (AC.getCFG())
       runLifetimeSafetyAnalysis(AC, &SemaHelper, LSStats, S.CollectStats);

diff  --git a/clang/lib/Sema/SemaLifetimeSafety.h 
b/clang/lib/Sema/SemaLifetimeSafety.h
index e6f7e3d929f61..c56a9692abe1a 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -133,7 +133,7 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
 
   void suggestLifetimeboundToParmVar(SuggestionScope Scope,
                                      const ParmVarDecl *ParmToAnnotate,
-                                     const Expr *EscapeExpr) override {
+                                     EscapingTarget Target) override {
     unsigned DiagID =
         (Scope == SuggestionScope::CrossTU)
             ? diag::warn_lifetime_safety_cross_tu_param_suggestion
@@ -150,9 +150,15 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
     S.Diag(ParmToAnnotate->getBeginLoc(), DiagID)
         << ParmToAnnotate->getSourceRange()
         << FixItHint::CreateInsertion(InsertionPoint, FixItText);
-    S.Diag(EscapeExpr->getBeginLoc(),
-           diag::note_lifetime_safety_suggestion_returned_here)
-        << EscapeExpr->getSourceRange();
+
+    if (const auto *EscapeExpr = Target.dyn_cast<const Expr *>())
+      S.Diag(EscapeExpr->getBeginLoc(),
+             diag::note_lifetime_safety_suggestion_returned_here)
+          << EscapeExpr->getSourceRange();
+    else if (const auto *EscapeField = Target.dyn_cast<const FieldDecl *>())
+      S.Diag(EscapeField->getLocation(),
+             diag::note_lifetime_safety_escapes_to_field_here)
+          << EscapeField->getSourceRange();
   }
 
   void suggestLifetimeboundToImplicitThis(SuggestionScope Scope,

diff  --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp 
b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index 0ed151b9db136..2b6c2d3d8bdb6 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -1,7 +1,5 @@
 // RUN: %clang_cc1 -fsyntax-only -Wdangling -Wdangling-field 
-Wreturn-stack-address -verify %s
-// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling 
-verify=cfg,cfg-field %s
-
-// FIXME: cfg-field should be detected in end-of-TU analysis but it doesn't 
work for constructors!
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify=cfg %s
 // RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference 
-fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety -Wno-dangling 
-verify=cfg %s
 
 #include "Inputs/lifetime-analysis.h"
@@ -84,17 +82,17 @@ void dangligGslPtrFromTemporary() {
 
 struct DanglingGslPtrField {
   MyIntPointer p; // expected-note {{pointer member declared here}} \
-                  // cfg-field-note 3 {{this field dangles}}
+                  // cfg-note 3 {{this field dangles}}
   MyLongPointerFromConversion p2; // expected-note {{pointer member declared 
here}} \
-                                  // cfg-field-note 2 {{this field dangles}}
+                                  // cfg-note 2 {{this field dangles}}
 
-  DanglingGslPtrField(int i) : p(&i) {} // cfg-field-warning {{address of 
stack memory escapes to a field}}
+  DanglingGslPtrField(int i) : p(&i) {} // cfg-warning {{address of stack 
memory escapes to a field}}
   DanglingGslPtrField() : p2(MyLongOwnerWithConversion{}) {}  // 
expected-warning {{initializing pointer member 'p2' to point to a temporary 
object whose lifetime is shorter than the lifetime of the constructed object}} \
-                                                              // 
cfg-field-warning {{address of stack memory escapes to a field}}
+                                                              // cfg-warning 
{{address of stack memory escapes to a field}}
   DanglingGslPtrField(double) : p(MyIntOwner{}) {}  // expected-warning 
{{initializing pointer member 'p' to point to a temporary object whose lifetime 
is shorter than the lifetime of the constructed object}} \
-                                                    // cfg-field-warning 
{{address of stack memory escapes to a field}}
-  DanglingGslPtrField(MyIntOwner io) : p(io) {} // cfg-field-warning {{address 
of stack memory escapes to a field}}
-  DanglingGslPtrField(MyLongOwnerWithConversion lo) : p2(lo) {} // 
cfg-field-warning {{address of stack memory escapes to a field}}
+                                                    // cfg-warning {{address 
of stack memory escapes to a field}}
+  DanglingGslPtrField(MyIntOwner io) : p(io) {} // cfg-warning {{address of 
stack memory escapes to a field}}
+  DanglingGslPtrField(MyLongOwnerWithConversion lo) : p2(lo) {} // cfg-warning 
{{address of stack memory escapes to a field}}
 };
 
 MyIntPointer danglingGslPtrFromLocal() {
@@ -350,11 +348,11 @@ const char *trackThroughMultiplePointer() {
 
 struct X {
   X(std::unique_ptr<int> up) :
-    pointee(*up),             // cfg-field-warning {{may have been moved.}}
-    pointee2(up.get()),       // cfg-field-warning {{may have been moved.}}
-    pointer(std::move(up)) {} // cfg-field-note 2 {{potentially moved here}}
-  int &pointee;               // cfg-field-note {{this field dangles}}
-  int *pointee2;              // cfg-field-note {{this field dangles}}
+    pointee(*up),             // cfg-warning {{may have been moved.}}
+    pointee2(up.get()),       // cfg-warning {{may have been moved.}}
+    pointer(std::move(up)) {} // cfg-note 2 {{potentially moved here}}
+  int &pointee;               // cfg-note {{this field dangles}}
+  int *pointee2;              // cfg-note {{this field dangles}}
   std::unique_ptr<int> pointer;
 };
 
@@ -365,9 +363,9 @@ struct X2 {
   // A common usage that moves the passing owner to the class.
   // verify a strict warning on this case.
   X2(XOwner owner) :
-    pointee(owner.get()),       // cfg-field-warning {{may have been moved.}}
-    owner(std::move(owner)) {}  // cfg-field-note {{potentially moved here}}
-  int* pointee;                 // cfg-field-note {{this field dangles}}
+    pointee(owner.get()),       // cfg-warning {{may have been moved.}}
+    owner(std::move(owner)) {}  // cfg-note {{potentially moved here}}
+  int* pointee;                 // cfg-note {{this field dangles}}
   XOwner owner;
 };
 
@@ -1116,10 +1114,10 @@ struct Foo2 {
 };
 
 struct Test {
-  Test(Foo2 foo) : bar(foo.bar.get()),  // cfg-field-warning-re {{address of 
stack memory escapes to a field. {{.*}} may have been moved}}
-      storage(std::move(foo.bar)) {};   // cfg-field-note {{potentially moved 
here}}
+  Test(Foo2 foo) : bar(foo.bar.get()),  // cfg-warning-re {{address of stack 
memory escapes to a field. {{.*}} may have been moved}}
+      storage(std::move(foo.bar)) {};   // cfg-note {{potentially moved here}}
 
-  Bar* bar; // cfg-field-note {{this field dangles}}
+  Bar* bar; // cfg-note {{this field dangles}}
   std::unique_ptr<Bar> storage;
 };
 

diff  --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp 
b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index 19c3251b9c296..c96b92cfe0b5b 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -456,3 +456,61 @@ S forward(const MyObj &obj) { // expected-warning 
{{parameter in intra-TU functi
 }
 
 } // namespace track_origins_for_lifetimebound_record_type
+
+namespace capturing_constructor {
+struct CaptureRefToView {
+  View v; // expected-note {{escapes to this field}}
+  CaptureRefToView(const MyObj& obj) : v(obj) {} // expected-warning 
{{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
+};
+
+CaptureRefToView test_ref_to_view() {
+  MyObj obj;
+  CaptureRefToView x(obj); // expected-warning {{address of stack memory is 
returned later}}
+  return x; // expected-note {{returned here}}
+}
+
+struct CaptureRefToPtr {
+  const MyObj* p; // expected-note {{escapes to this field}}
+  CaptureRefToPtr(const MyObj& obj) : p(&obj) {} // expected-warning 
{{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
+};
+
+CaptureRefToPtr test_ref_to_ptr() {
+  MyObj obj;
+  CaptureRefToPtr x(obj); // expected-warning {{address of stack memory is 
returned later}}
+  return x; // expected-note {{returned here}}
+}
+
+struct CaptureViewToView {
+  View v; // expected-note {{escapes to this field}}
+  CaptureViewToView(View v_param) : v(v_param) {} // expected-warning 
{{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
+};
+
+CaptureViewToView test_view_to_view() {
+  MyObj obj;
+  View v(obj); // expected-warning {{address of stack memory is returned 
later}}
+  CaptureViewToView x(v);
+  return x; // expected-note {{returned here}}
+}
+
+struct CapturePtrToPtr {
+  const MyObj* p; // expected-note {{escapes to this field}}
+  CapturePtrToPtr(const MyObj* p_param) : p(p_param) {} // expected-warning 
{{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
+};
+
+CapturePtrToPtr test_ptr_to_ptr() {
+  MyObj obj;
+  CapturePtrToPtr x(&obj); // expected-warning {{address of stack memory is 
returned later}}
+  return x; // expected-note {{returned here}}
+}
+
+struct CaptureRefToRef {
+  const MyObj& r; // expected-note {{escapes to this field}}
+  CaptureRefToRef(const MyObj& obj) : r(obj) {} // expected-warning 
{{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
+};
+
+CaptureRefToRef test_ref_to_ref() {
+  MyObj obj;
+  CaptureRefToRef x(obj); // expected-warning {{address of stack memory is 
returned later}}
+  return x; // expected-note {{returned here}}
+}
+} // namespace capturing_constructor

diff  --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 77d8e3370676d..b1dea7f7340b3 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2275,8 +2275,8 @@ void from_template_instantiation() {
 }
 
 struct FieldInitFromLifetimebound {
-  S value; // function-note {{this field dangles}}
-  FieldInitFromLifetimebound() : value(getS(std::string("temp"))) {} // 
function-warning {{address of stack memory escapes to a field}}
+  S value; // expected-note {{this field dangles}}
+  FieldInitFromLifetimebound() : value(getS(std::string("temp"))) {} // 
expected-warning {{address of stack memory escapes to a field}}
 };
 
 S S::return_self_after_registration() const {
@@ -2467,13 +2467,13 @@ void nested_local_pointer() {
 }
 
 struct PFieldFromParam {
-  Pointer<Bar> value;                      // function-note {{this field 
dangles}}
-  PFieldFromParam(Bar bar) : value(bar) {} // function-warning {{address of 
stack memory escapes to a field}}
+  Pointer<Bar> value;                      // expected-note {{this field 
dangles}}
+  PFieldFromParam(Bar bar) : value(bar) {} // expected-warning {{address of 
stack memory escapes to a field}}
 };
 
 struct PFieldFromTemp {
-  Pointer<Bar> value;                // function-note {{this field dangles}}
-  PFieldFromTemp() : value(Bar{}) {} // function-warning {{address of stack 
memory escapes to a field}}
+  Pointer<Bar> value;                // expected-note {{this field dangles}}
+  PFieldFromTemp() : value(Bar{}) {} // expected-warning {{address of stack 
memory escapes to a field}}
 };
 
 } // namespace gslpointer_construction_from_lifetimebound


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to