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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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() << ")"; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
