[clang] Suggest lifetime annotations [Issue Id: 169375] (PR #169767)
Kashika Akhouri via cfe-commits
cfe-commits at lists.llvm.org
Thu Nov 27 01:56:48 PST 2025
https://github.com/kashika0112 updated https://github.com/llvm/llvm-project/pull/169767
>From 00494b03dd31a4a1e541fb45bd524ee452541208 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Thu, 27 Nov 2025 07:13:49 +0000
Subject: [PATCH 1/3] 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;
+}
>From 474aa135a28b98559b242de478c31f816b9eeddd Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Thu, 27 Nov 2025 09:44:17 +0000
Subject: [PATCH 2/3] Remove access path from placeholder loans
---
clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h | 2 +-
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index 16d4c834c8071..07025e1e80d11 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -31,7 +31,7 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) {
struct AccessPath {
const clang::ValueDecl *D;
- AccessPath(const clang::ValueDecl *D) : D(D) {}
+ AccessPath(const clang::ValueDecl *D = nullptr) : D(D) {}
};
/// Information about a single borrow, or "Loan". A loan is created when a
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 9ff4bdf4b90ff..ffd57e00bb722 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -361,7 +361,7 @@ llvm::SmallVector<Fact *> FactsGenerator::createPlaceholderLoanFacts() {
continue;
if (ParamType->isPointerType() || ParamType->isReferenceType() ||
isGslPointerType(ParamType)) {
- Loan &L = FactMgr.getLoanMgr().addLoan({PVD}, /*IssueExpr=*/nullptr);
+ Loan &L = FactMgr.getLoanMgr().addLoan({}, /*IssueExpr=*/nullptr);
FactMgr.getLoanMgr().addPlaceholderLoan(L.ID, PVD);
OriginID OID = FactMgr.getOriginMgr().getOrCreate(*PVD);
PlaceholderLoanFacts.push_back(FactMgr.createFact<IssueFact>(L.ID, OID));
>From 0d044d78093be9ebd26d254d6b06a6dff97f8b67 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Thu, 27 Nov 2025 09:56:25 +0000
Subject: [PATCH 3/3] Update loan dump to handle nullptr
---
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 5 +----
clang/lib/Analysis/LifetimeSafety/Loans.cpp | 4 ++++
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 823c4d19e13b9..086dcf9fb4538 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -21,10 +21,7 @@ void IssueFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,
const OriginManager &OM) const {
const Loan &L = LM.getLoan(getLoanID());
OS << "Issue (";
- if (L.IssueExpr == nullptr)
- OS << getLoanID() << " (Placeholder loan) ";
- else
- L.dump(OS);
+ L.dump(OS);
OS << ", ToOrigin: ";
OM.dump(getOriginID(), OS);
OS << ")\n";
diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
index 2c85a3c6083f3..a0886cb583e04 100644
--- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
@@ -11,6 +11,10 @@
namespace clang::lifetimes::internal {
void Loan::dump(llvm::raw_ostream &OS) const {
+ if (Path.D == nullptr) {
+ OS << ID << " (Placeholder loan) ";
+ return;
+ }
OS << ID << " (Path: ";
OS << Path.D->getNameAsString() << ")";
}
More information about the cfe-commits
mailing list