https://github.com/kashika0112 created 
https://github.com/llvm/llvm-project/pull/169767

Add lifetime annotation suggestion in lifetime analysis.

This PR introduces a new feature to Clang's lifetime analysis to detect and 
suggest missing `[[clang::lifetimebound]]` annotations on function parameters.
It introduces the concept of `placeholder loans`. At the entry of a function, a 
special placeholder loan is created for each pointer or reference parameter. 
The analysis then tracks these loans using `OriginFlow` facts. If an 
`OriginEscapesFact` shows that an origin holding a placeholder loan escapes the 
function's scope (e.g., via a return statement), a new warning is issued.

This warning, controlled by the `-Wexperimental-lifetime-suggestions flag`, 
suggests adding the `[[clang::lifetimebound]]` attribute to the corresponding 
parameter.

Example:
```
std::string_view foo(std::string_view a) {
 return a;
}
```
Facts:
```
Function: foo
  Block B2:
    Issue (0 (Placeholder loan) , ToOrigin: 0 (Decl: a))
  End of Block
  Block B1:
    Use (0 (Decl: a), Read)
    OriginFlow (Dest: 1 (Expr: ImplicitCastExpr), Src: 0 (Decl: a))
    OriginFlow (Dest: 2 (Expr: CXXConstructExpr), Src: 1 (Expr: 
ImplicitCastExpr))
    OriginEscapes (2 (Expr: CXXConstructExpr))
  End of Block
  Block B0:
  End of Block
```
Sample warning:
```
o.cpp:61:39: warning: param should be marked [[clang::lifetimebound]] 
[-Wexperimental-lifetime-safety-suggestions]
   61 | std::string_view foo(std::string_view a) {
      |                      ~~~~~~~~~~~~~~~~~^
      |                      [[clang::lifetimebound]] 
o.cpp:62:9: note: param escapes here
   62 |  return a;
```



>From 00494b03dd31a4a1e541fb45bd524ee452541208 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <[email protected]>
Date: Thu, 27 Nov 2025 07:13:49 +0000
Subject: [PATCH] Add lifetime annotation suggestion

---
 .../Analyses/LifetimeSafety/FactsGenerator.h  |  2 +
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |  3 +
 .../Analysis/Analyses/LifetimeSafety/Loans.h  | 14 ++++
 clang/include/clang/Basic/DiagnosticGroups.td |  2 +
 .../clang/Basic/DiagnosticSemaKinds.td        |  7 ++
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 29 ++++++++
 clang/lib/Analysis/LifetimeSafety/Facts.cpp   |  6 +-
 .../LifetimeSafety/FactsGenerator.cpp         | 28 ++++++++
 clang/lib/Sema/AnalysisBasedWarnings.cpp      | 11 +++
 clang/test/Sema/warn-lifetime-safety.cpp      | 72 ++++++++++++++++++-
 10 files changed, 172 insertions(+), 2 deletions(-)

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 878cb90b685f9..818133eab261d 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -91,6 +91,8 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
 
   void markUseAsWrite(const DeclRefExpr *DRE);
 
+  llvm::SmallVector<Fact *> createPlaceholderLoanFacts();
+
   FactManager &FactMgr;
   AnalysisDeclContext &AC;
   llvm::SmallVector<Fact *> CurrentBlockFacts;
diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index b34a7f18b5809..5397263315010 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -47,6 +47,9 @@ class LifetimeSafetyReporter {
                                     const Expr *EscapeExpr,
                                     SourceLocation ExpiryLoc,
                                     Confidence Confidence) {}
+
+  virtual void reportMissingAnnotations(const ParmVarDecl *PVD,
+                                        const Expr *EscapeExpr) {}
 };
 
 /// The main entry point for the analysis.
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index 7f5cf03fd3e5f..16d4c834c8071 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -67,6 +67,15 @@ class LoanManager {
   }
   llvm::ArrayRef<Loan> getLoans() const { return AllLoans; }
 
+  void addPlaceholderLoan(LoanID LID, const ParmVarDecl *PVD) {
+    PlaceholderLoans[LID] = PVD;
+  }
+
+  const llvm::DenseMap<LoanID, const ParmVarDecl *> &
+  getPlaceholderLoans() const {
+    return PlaceholderLoans;
+  }
+
 private:
   LoanID getNextLoanID() { return NextLoanID++; }
 
@@ -74,6 +83,11 @@ class LoanManager {
   /// TODO(opt): Profile and evaluate the usefullness of small buffer
   /// optimisation.
   llvm::SmallVector<Loan> AllLoans;
+  /// Represents a map of placeholder LoanID to the function parameter.
+  /// Placeholder loans are dummy loans created for each pointer or reference
+  /// parameter to represent a borrow from the function's caller, which the
+  /// analysis tracks to see if it unsafely escapes the function's scope.
+  llvm::DenseMap<LoanID, const ParmVarDecl *> PlaceholderLoans;
 };
 } // namespace clang::lifetimes::internal
 
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 2fff32bbc4d6c..d2537870bfd64 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -541,6 +541,8 @@ def LifetimeSafety : 
DiagGroup<"experimental-lifetime-safety",
     Experimental warnings to detect use-after-free and related temporal safety 
bugs based on lifetime safety analysis.
   }];
 }
+def LifetimeSafetySuggestions
+    : DiagGroup<"experimental-lifetime-safety-suggestions">;
 
 def DistributedObjectModifiers : DiagGroup<"distributed-object-modifiers">;
 def DllexportExplicitInstantiationDecl : 
DiagGroup<"dllexport-explicit-instantiation-decl">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 4a145fd71eedd..3a4949ac9a5d6 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10778,6 +10778,13 @@ def note_lifetime_safety_used_here : Note<"later used 
here">;
 def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
 def note_lifetime_safety_returned_here : Note<"returned here">;
 
+def warn_lifetime_param_should_be_lifetimebound
+    : Warning<"param should be marked [[clang::lifetimebound]]">,
+      InGroup<LifetimeSafetySuggestions>,
+      DefaultIgnore;
+
+def note_lifetime_escapes_here : Note<"param escapes here">;
+
 // For non-floating point, expressions of the form x == x or x != x
 // should result in a warning, since these always evaluate to a constant.
 // Array comparisons have similar warnings
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 1f7c282dadac2..6b1d0f619bb4d 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -50,6 +50,7 @@ struct PendingWarning {
 class LifetimeChecker {
 private:
   llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
+  llvm::DenseMap<const ParmVarDecl *, const Expr *> AnnotationWarningsMap;
   const LoanPropagationAnalysis &LoanPropagation;
   const LiveOriginsAnalysis &LiveOrigins;
   const FactManager &FactMgr;
@@ -65,7 +66,28 @@ class LifetimeChecker {
       for (const Fact *F : FactMgr.getFacts(B))
         if (const auto *EF = F->getAs<ExpireFact>())
           checkExpiry(EF);
+        else if (const auto *OEF = F->getAs<OriginEscapesFact>())
+          checkAnnotations(OEF);
     issuePendingWarnings();
+    issueAnnotationWarnings();
+  }
+
+  /// Checks if an escaping origin holds a placeholder loan, indicating a
+  /// missing [[clang::lifetimebound]] annotation.
+  void checkAnnotations(const OriginEscapesFact *OEF) {
+    if (!Reporter)
+      return;
+    const auto &PlaceholderLoansMap =
+        FactMgr.getLoanMgr().getPlaceholderLoans();
+    if (PlaceholderLoansMap.empty())
+      return;
+    OriginID EscapedOID = OEF->getEscapedOriginID();
+    LoanSet EscapedLoans = LoanPropagation.getLoans(EscapedOID, OEF);
+    for (LoanID LID : EscapedLoans) {
+      if (auto It = PlaceholderLoansMap.find(LID);
+          It != PlaceholderLoansMap.end())
+        AnnotationWarningsMap.try_emplace(It->second, OEF->getEscapeExpr());
+    }
   }
 
   /// Checks for use-after-free & use-after-return errors when a loan expires.
@@ -132,6 +154,13 @@ class LifetimeChecker {
         llvm_unreachable("Unhandled CausingFact type");
     }
   }
+
+  void issueAnnotationWarnings() {
+    if (!Reporter)
+      return;
+    for (const auto &[PVD, EscapeExpr] : AnnotationWarningsMap)
+      Reporter->reportMissingAnnotations(PVD, EscapeExpr);
+  }
 };
 } // namespace
 
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp 
b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 0ae7111c489e8..823c4d19e13b9 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -19,8 +19,12 @@ void Fact::dump(llvm::raw_ostream &OS, const LoanManager &,
 
 void IssueFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,
                      const OriginManager &OM) const {
+  const Loan &L = LM.getLoan(getLoanID());
   OS << "Issue (";
-  LM.getLoan(getLoanID()).dump(OS);
+  if (L.IssueExpr == nullptr)
+    OS << getLoanID() << " (Placeholder loan) ";
+  else
+    L.dump(OS);
   OS << ", ToOrigin: ";
   OM.dump(getOriginID(), OS);
   OS << ")\n";
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 00870c3fd4086..9ff4bdf4b90ff 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -42,11 +42,16 @@ static const Loan *createLoan(FactManager &FactMgr, const 
DeclRefExpr *DRE) {
 
 void FactsGenerator::run() {
   llvm::TimeTraceScope TimeProfile("FactGenerator");
+  const CFG &Cfg = *AC.getCFG();
+  llvm::SmallVector<Fact *> PlaceholderLoanFacts = 
createPlaceholderLoanFacts();
   // Iterate through the CFG blocks in reverse post-order to ensure that
   // initializations and destructions are processed in the correct sequence.
   for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
     CurrentBlockFacts.clear();
     EscapesInCurrentBlock.clear();
+    if (Block->getBlockID() == Cfg.getEntry().getBlockID())
+      CurrentBlockFacts.append(PlaceholderLoanFacts.begin(),
+                               PlaceholderLoanFacts.end());
     for (unsigned I = 0; I < Block->size(); ++I) {
       const CFGElement &Element = Block->Elements[I];
       if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
@@ -342,4 +347,27 @@ void FactsGenerator::markUseAsWrite(const DeclRefExpr 
*DRE) {
   UseFacts[DRE]->markAsWritten();
 }
 
+// Creates an IssueFact for a new placeholder loan for each pointer or 
reference
+// parameter at the function's entry.
+llvm::SmallVector<Fact *> FactsGenerator::createPlaceholderLoanFacts() {
+  llvm::SmallVector<Fact *> PlaceholderLoanFacts;
+  const auto *FD = dyn_cast<FunctionDecl>(AC.getDecl());
+  if (!FD)
+    return PlaceholderLoanFacts;
+
+  for (const ParmVarDecl *PVD : FD->parameters()) {
+    QualType ParamType = PVD->getType();
+    if (PVD->hasAttr<LifetimeBoundAttr>())
+      continue;
+    if (ParamType->isPointerType() || ParamType->isReferenceType() ||
+        isGslPointerType(ParamType)) {
+      Loan &L = FactMgr.getLoanMgr().addLoan({PVD}, /*IssueExpr=*/nullptr);
+      FactMgr.getLoanMgr().addPlaceholderLoan(L.ID, PVD);
+      OriginID OID = FactMgr.getOriginMgr().getOrCreate(*PVD);
+      PlaceholderLoanFacts.push_back(FactMgr.createFact<IssueFact>(L.ID, OID));
+    }
+  }
+  return PlaceholderLoanFacts;
+}
+
 } // namespace clang::lifetimes::internal
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 43d2b9a829545..666f8dabbd4cb 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2884,6 +2884,17 @@ class LifetimeSafetyReporterImpl : public 
LifetimeSafetyReporter {
         << EscapeExpr->getEndLoc();
   }
 
+  void reportMissingAnnotations(const ParmVarDecl *PVD,
+                                const Expr *EscapeExpr) override {
+    S.Diag(PVD->getLocation(),
+           diag::warn_lifetime_param_should_be_lifetimebound)
+        << PVD->getSourceRange()
+        << FixItHint::CreateInsertion(
+               PVD->getTypeSourceInfo()->getTypeLoc().getBeginLoc(),
+               "[[clang::lifetimebound]] ");
+    S.Diag(EscapeExpr->getBeginLoc(), diag::note_lifetime_escapes_here);
+  }
+
 private:
   Sema &S;
 };
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 1191469e23df1..4318ec4c4cb2f 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety -Wno-dangling -verify %s
+// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety -Wexperimental-lifetime-safety-suggestions 
-Wno-dangling -verify %s
 
 struct MyObj {
   int id;
@@ -943,3 +943,73 @@ void parentheses(bool cond) {
   }  // expected-note 4 {{destroyed here}}
   (void)*p;  // expected-note 4 {{later used here}}
 }
+
+//===----------------------------------------------------------------------===//
+// Lifetimebound Annotation Suggestion Tests
+//===----------------------------------------------------------------------===//
+
+View return_view_directly (View a // expected-warning {{param should be marked 
[[clang::lifetimebound]]}}.
+) {
+  return a; // expected-note {{param escapes here}}
+}
+
+View conditional_return_view (
+    View a, // expected-warning {{param should be marked 
[[clang::lifetimebound]]}}.
+    View b,  // expected-warning {{param should be marked 
[[clang::lifetimebound]]}}.
+    bool c
+) {
+  View res;
+  if (c)  
+    res = a;                    
+  else
+    res = b;          
+  return res;  // expected-note 2 {{param escapes here}} 
+}
+
+// FIXME: Fails to generate lifetime suggestion for reference types as these 
are not handled currently.
+MyObj& return_reference (
+  MyObj& a,   
+  MyObj& b,   
+  bool c
+) {
+  if(c) {
+    return a;   
+  }
+  return b;     
+}
+
+// FIXME: Fails to generate lifetime suggestion for reference types as these 
are not handled currently.
+View return_view_from_reference (
+    MyObj& p 
+) {
+  return p; 
+}
+
+int* return_pointer_directly (int* a // expected-warning {{param should be 
marked [[clang::lifetimebound]]}}.
+) {
+  return a;               // expected-note {{param escapes here}} 
+}
+
+MyObj* return_pointer_object (MyObj* a // expected-warning {{param should be 
marked [[clang::lifetimebound]]}}.
+) {
+  return a;                           // expected-note {{param escapes here}} 
+}
+
+View only_one_paramter_annotated (View a [[clang::lifetimebound]], 
+  View b, // expected-warning {{param should be marked 
[[clang::lifetimebound]]}}.
+  bool c
+) {
+ if(c)
+  return a;
+ return b; // expected-note {{param escapes here}} 
+}
+
+// Safe cases
+View already_annotated(View a [[clang::lifetimebound]]) {
+ return a;
+}
+
+// Safe cases
+MyObj return_obj_by_value(MyObj& p) {
+  return p;
+}

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

Reply via email to