https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/229784
>From 8474b6182e3ffa2cfa69260101ce71b548fac64b Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Wed, 7 Oct 2026 14:10:53 +0000 Subject: [PATCH] field-origin-escape --- .../Analyses/LifetimeSafety/LifetimeSafety.h | 6 ++ .../Analysis/Analyses/LifetimeSafety/Loans.h | 43 +++++--- clang/include/clang/Basic/DiagnosticGroups.td | 7 ++ .../clang/Basic/DiagnosticSemaKinds.td | 5 + clang/lib/Analysis/LifetimeSafety/Checker.cpp | 26 +++++ .../LifetimeSafety/FactsGenerator.cpp | 15 ++- clang/lib/Analysis/LifetimeSafety/Loans.cpp | 16 +++ clang/lib/Sema/SemaLifetimeSafety.h | 16 +++ .../LifetimeSafety/annotation-suggestions.cpp | 97 ++++++++++++++++++- 9 files changed, 212 insertions(+), 19 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 6854902059e5017..e6fbc2a5caee720 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -46,6 +46,7 @@ struct LifetimeSafetyOpts { bool CheckLifetimeboundViolations; bool CheckMisplacedLifetimebound; bool CheckInapplicableLifetimebound; + bool CheckFieldOriginEscape; }; /// Enum to track functions visible across or within TU. @@ -164,6 +165,11 @@ class LifetimeSafetySemaHelper { const CXXMethodDecl *MD, const Expr *EscapeExpr) {} + // Reports when a member function returns an origin of a field. + virtual void reportFieldOriginEscape(const CXXMethodDecl *MD, + const FieldDecl *FD, + const Expr *EscapeExpr) {} + // Adds inferred lifetime bound attribute for implicit this to its // TypeSourceInfo. virtual void addLifetimeBoundToImplicitThis(const CXXMethodDecl *MD) {} diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h index 00c931e4f9f66c6..cf70c132fc18a07 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h @@ -70,32 +70,42 @@ class PathElement { const FieldDecl *FD; }; -/// Represents the base of a placeholder access path, which is either a -/// function parameter or the implicit 'this' object of an instance method. -/// Placeholder paths never expire within the function scope, as they represent -/// storage from the caller's scope. +/// Represents the base of a placeholder access path, which is a function +/// parameter, the implicit 'this' object of an instance method, or a field of +/// the enclosing class. Placeholder paths never expire within the function +/// scope, as they represent storage from the caller's or object's scope. class PlaceholderBase : public llvm::FoldingSetNode { - llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *> ParamOrMethod; + llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *, + const FieldDecl *> + ParamMethodOrField; public: - PlaceholderBase(const ParmVarDecl *PVD) : ParamOrMethod(PVD) {} - PlaceholderBase(const CXXMethodDecl *MD) : ParamOrMethod(MD) {} + PlaceholderBase(const ParmVarDecl *PVD) : ParamMethodOrField(PVD) {} + PlaceholderBase(const CXXMethodDecl *MD) : ParamMethodOrField(MD) {} + PlaceholderBase(const FieldDecl *FD) : ParamMethodOrField(FD) {} const ParmVarDecl *getParmVarDecl() const { - return ParamOrMethod.dyn_cast<const ParmVarDecl *>(); + return ParamMethodOrField.dyn_cast<const ParmVarDecl *>(); } const CXXMethodDecl *getImplicitThisParent() const { - return ParamOrMethod.dyn_cast<const CXXMethodDecl *>(); + return ParamMethodOrField.dyn_cast<const CXXMethodDecl *>(); } - using KeyTy = llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>; + const FieldDecl *getFieldDecl() const { + return ParamMethodOrField.dyn_cast<const FieldDecl *>(); + } + + using KeyTy = llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *, + const FieldDecl *>; - static void Profile(llvm::FoldingSetNodeID &ID, KeyTy ParamOrMethod) { - ID.AddPointer(ParamOrMethod.getOpaqueValue()); + static void Profile(llvm::FoldingSetNodeID &ID, KeyTy ParamMethodOrField) { + ID.AddPointer(ParamMethodOrField.getOpaqueValue()); } - void Profile(llvm::FoldingSetNodeID &ID) const { Profile(ID, ParamOrMethod); } + void Profile(llvm::FoldingSetNodeID &ID) const { + Profile(ID, ParamMethodOrField); + } }; /// Represents the storage location being borrowed, e.g., a specific stack @@ -229,6 +239,9 @@ class LoanManager { Loan *createPlaceholderLoan(const CXXMethodDecl *MD) { return createLoan(AccessPath(getOrCreatePlaceholderBase(MD))); } + Loan *createPlaceholderLoan(const FieldDecl *FD) { + return createLoan(AccessPath(getOrCreatePlaceholderBase(FD))); + } const Loan *getLoan(LoanID ID) const { assert(ID.Value < AllLoans.size()); @@ -240,9 +253,11 @@ class LoanManager { private: LoanID getNextLoanID() { return NextLoanID++; } - /// Gets or creates a placeholder base for a given parameter or method. + /// Gets or creates a placeholder base for a given parameter, method, or + /// field. const PlaceholderBase *getOrCreatePlaceholderBase(const ParmVarDecl *PVD); const PlaceholderBase *getOrCreatePlaceholderBase(const CXXMethodDecl *MD); + const PlaceholderBase *getOrCreatePlaceholderBase(const FieldDecl *FD); LoanID NextLoanID{0}; diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 1da7698944b24b2..5ad1136d66fea15 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -744,6 +744,13 @@ Validates correct usage and adherence to lifetime safety annotations like [[clan }]; } +def LifetimeSafetyFieldOriginEscape + : DiagGroup<"lifetime-safety-field-origin-escape"> { + code Documentation = [{ +Detects member functions returning an origin of a field (such as a pointer, reference, or view field). + }]; +} + def LifetimeSafetyAll : DiagGroup<"lifetime-safety-all", [LifetimeSafety, LifetimeSafetySuggestions, diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 9208aba1445d7eb..1b333bdb60d2e0b 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11143,6 +11143,11 @@ def warn_lifetime_safety_noescape_escapes InGroup<LifetimeSafetyNoescape>, DefaultIgnore; +def warn_lifetime_safety_field_origin_escape + : Warning<"return value depends on the lifetime of the object referenced by %0, which cannot be expressed with [[clang::lifetimebound]]">, + InGroup<LifetimeSafetyFieldOriginEscape>, + DefaultIgnore; + // 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 fbf345460b6d859..adb866567aa6a78 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -60,6 +60,7 @@ class LifetimeChecker { llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap; llvm::DenseMap<AnnotationTarget, EscapingTarget> AnnotationWarningsMap; llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap; + llvm::DenseMap<const FieldDecl *, const Expr *> FieldOriginEscapesMap; llvm::DenseSet<const Decl *> VerifiedLiftimeboundEscapes; const LoanPropagationAnalysis &LoanPropagation; const MovedLoansAnalysis &MovedLoans; @@ -105,6 +106,8 @@ class LifetimeChecker { checkAnnotations(OEF); issuePendingWarnings(); suggestAnnotations(); + if (LSOpts.CheckFieldOriginEscape) + reportFieldOriginEscapes(); if (LSOpts.CheckNoescapeViolations) reportNoescapeViolations(); if (LSOpts.CheckLifetimeboundViolations) @@ -171,6 +174,16 @@ class LifetimeChecker { AnnotationWarningsMap.try_emplace(MD, ReturnEsc->getReturnExpr()); } }; + auto CheckField = [&](const FieldDecl *Field) { + if (!LSOpts.CheckFieldOriginEscape) + return; + if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF)) { + if (OriginList *RetList = FactMgr.getOriginMgr().getOrCreateList( + ReturnEsc->getReturnExpr()); + RetList && RetList->getOuterOriginID() == EscapedOID) + FieldOriginEscapesMap.try_emplace(Field, ReturnEsc->getReturnExpr()); + } + }; auto MovedAtEscape = MovedLoans.getMovedLoans(OEF); for (LoanID LID : EscapedLoans) { const Loan *L = FactMgr.getLoanMgr().getLoan(LID); @@ -181,6 +194,8 @@ class LifetimeChecker { CheckParam(PVD, /*IsMoved=*/MovedAtEscape.lookup(LID)); else if (const auto *MD = PB->getImplicitThisParent()) CheckImplicitThis(MD); + else if (const auto *Field = PB->getFieldDecl()) + CheckField(Field); } } @@ -446,6 +461,17 @@ class LifetimeChecker { } } + void reportFieldOriginEscapes() { + if (!SemaHelper) + return; + const auto *MD = dyn_cast<CXXMethodDecl>(FD); + if (!MD) + return; + llvm::TimeTraceScope TimeTrace("ReportFieldOriginEscapes"); + for (auto [Field, EscapeExpr] : FieldOriginEscapesMap) + SemaHelper->reportFieldOriginEscape(MD, Field, EscapeExpr); + } + void reportNoescapeViolations() { llvm::TimeTraceScope TimeTrace("ReportNoescapeViolations"); for (auto [PVD, EscapeTarget] : NoescapeWarningsMap) { diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index d1113783ab882ab..ba3106517e88d87 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -1214,10 +1214,21 @@ llvm::SmallVector<Fact *> FactsGenerator::issuePlaceholderLoans() { llvm::SmallVector<Fact *> PlaceholderLoanFacts; if (auto ThisOrigins = FactMgr.getOriginMgr().getThisOrigins()) { OriginList *List = *ThisOrigins; - const Loan *L = - FactMgr.getLoanMgr().createPlaceholderLoan(cast<CXXMethodDecl>(FD)); + const auto *MD = cast<CXXMethodDecl>(FD); + const Loan *L = FactMgr.getLoanMgr().createPlaceholderLoan(MD); PlaceholderLoanFacts.push_back( FactMgr.createFact<IssueFact>(L->getID(), List->getOuterOriginID())); + if (!isa<CXXConstructorDecl>(MD)) + if (const CXXRecordDecl *ClassDecl = MD->getParent()->getDefinition()) + for (const FieldDecl *Field : ClassDecl->fields()) { + OriginList *FieldList = getOriginsList(*Field); + if (!FieldList) + continue; + const Loan *FieldLoan = + FactMgr.getLoanMgr().createPlaceholderLoan(Field); + PlaceholderLoanFacts.push_back(FactMgr.createFact<IssueFact>( + FieldLoan->getID(), FieldList->getOuterOriginID())); + } } for (const ParmVarDecl *PVD : FD->parameters()) { OriginList *List = getOriginsList(*PVD); diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp index e6cbd922d20c724..c15f9b499b35b8e 100644 --- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp @@ -21,6 +21,8 @@ void AccessPath::dump(llvm::raw_ostream &OS) const { OS << "$" << PVD->getNameAsString(); else if (PB->getImplicitThisParent()) OS << "$this"; + else if (const auto *FD = PB->getFieldDecl()) + OS << "$this." << FD->getNameAsString(); } else if (const auto *E = getAsNewAllocation()) OS << "NewAllocation at " << E; else @@ -62,4 +64,18 @@ LoanManager::getOrCreatePlaceholderBase(const CXXMethodDecl *MD) { PlaceholderBases.insert(NewPB, InsertToken); return NewPB; } + +const PlaceholderBase * +LoanManager::getOrCreatePlaceholderBase(const FieldDecl *FD) { + llvm::FoldingSetNodeID ID; + PlaceholderBase::Profile(ID, FD); + llvm::FoldingSetInsertToken InsertToken; + if (PlaceholderBase *Existing = PlaceholderBases.lookup(ID, InsertToken)) + return Existing; + + void *Mem = LoanAllocator.Allocate<PlaceholderBase>(); + PlaceholderBase *NewPB = new (Mem) PlaceholderBase(FD); + PlaceholderBases.insert(NewPB, InsertToken); + return NewPB; +} } // namespace clang::lifetimes::internal diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 52ee8c0037b8d0f..3f2bfb4d4d9886f 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -72,6 +72,11 @@ inline bool ShouldCheckInapplicableLifetimebound(Sema &S, const Decl *D) { diag::warn_lifetime_safety_inapplicable_lifetimebound, D->getBeginLoc()); } +inline bool ShouldCheckFieldOriginEscape(Sema &S, const Decl *D) { + return !S.getDiagnostics().isIgnored( + diag::warn_lifetime_safety_field_origin_escape, D->getBeginLoc()); +} + inline bool ShouldSuggestLifetimeAnnotations(Sema &S, const Decl *D) { DiagnosticsEngine &Diags = S.getDiagnostics(); constexpr unsigned DiagIDs[] = { @@ -114,6 +119,7 @@ inline bool IsLifetimeSafetyEnabled(Sema &S, const Decl *D) { ShouldCheckLifetimeboundViolations(S, D) || ShouldCheckMisplacedLifetimebound(S, D) || ShouldCheckInapplicableLifetimebound(S, D) || + ShouldCheckFieldOriginEscape(S, D) || ShouldSuggestLifetimeAnnotations(S, D); } @@ -127,6 +133,7 @@ inline LifetimeSafetyOpts GetLifetimeSafetyOpts(Sema &S, const Decl *D) { LSOpts.CheckMisplacedLifetimebound = ShouldCheckMisplacedLifetimebound(S, D); LSOpts.CheckInapplicableLifetimebound = ShouldCheckInapplicableLifetimebound(S, D); + LSOpts.CheckFieldOriginEscape = ShouldCheckFieldOriginEscape(S, D); return LSOpts; } @@ -458,6 +465,15 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { << EscapeExpr->getSourceRange(); } + void reportFieldOriginEscape(const CXXMethodDecl *MD, const FieldDecl *FD, + const Expr *EscapeExpr) override { + S.Diag(MD->getLocation(), diag::warn_lifetime_safety_field_origin_escape) + << getDiagSubjectDescription(FD) << MD->getNameInfo().getSourceRange(); + + S.Diag(EscapeExpr->getBeginLoc(), diag::note_lifetime_safety_returned_here) + << EscapeExpr->getSourceRange(); + } + void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape, const Expr *EscapeExpr) override { S.Diag(ParmWithNoescape->getBeginLoc(), diff --git a/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp b/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp index 4deb6d1f8688d64..b095d48365b2cc1 100644 --- a/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp +++ b/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp @@ -1,8 +1,8 @@ // RUN: rm -rf %t // RUN: split-file %s %t -// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wlifetime-safety-annotation-placement -Wno-dangling -I%t -I%S -verify %t/test_source.cpp -// RUN: %clang_cc1 -fsyntax-only -std=c++23 -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -I%S -verify %t/test_source.cpp -// RUN: %clang_cc1 -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -I%S -fixit %t/test_source.cpp +// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wlifetime-safety-annotation-placement -Wlifetime-safety-field-origin-escape -Wno-dangling -I%t -I%S -verify %t/test_source.cpp +// RUN: %clang_cc1 -fsyntax-only -std=c++23 -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wlifetime-safety-field-origin-escape -Wno-dangling -I%t -I%S -verify %t/test_source.cpp +// RUN: %clang_cc1 -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wlifetime-safety-field-origin-escape -Wno-dangling -I%t -I%S -fixit %t/test_source.cpp // RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wno-dangling -I%t -I%S -Werror=lifetime-safety-suggestions %t/test_source.cpp View definition_before_header(View a); @@ -700,3 +700,94 @@ S foo() { return S(create_up()); } } // namespace GH193747 + +namespace field_origin_escape { + +class Context { +public: + // This method should be annotated with lifetimebound. + std::string_view method() const { // expected-warning {{implicit this in intra-TU function should be marked [[clang::lifetimebound]]}} + return method_; // expected-note {{param returned here}} + } + +private: + std::string method_ = "42"; +}; + +class ContextAnnotated { +public: + std::string_view method() const [[clang::lifetimebound]] { + return method_; + } + +private: + std::string method_ = "42"; +}; + +class ContextWrapper { +public: + explicit ContextWrapper(Context &inner, // expected-warning {{parameter in intra-TU constructor should be marked [[clang::lifetimebound]]}} + ContextAnnotated &annot) // expected-warning {{parameter in intra-TU constructor should be marked [[clang::lifetimebound]]}} + : inner_(inner), annot_(annot) {} + + Context &inner() const { // expected-warning {{return value depends on the lifetime of the object referenced by field 'inner_', which cannot be expressed with [[clang::lifetimebound]]}} + return inner_; // expected-note {{returned here}} + } + + // This method should NOT be annotated with lifetimebound. + std::string_view method() const { // expected-warning {{return value depends on the lifetime of the object referenced by field 'inner_', which cannot be expressed with [[clang::lifetimebound]]}} + return inner_.method(); // expected-note {{returned here}} + } + + std::string_view annot_method() const { // expected-warning {{return value depends on the lifetime of the object referenced by field 'annot_', which cannot be expressed with [[clang::lifetimebound]]}} + return annot_.method(); // expected-note {{returned here}} + } + +private: + Context &inner_; // expected-note {{escapes to this field}} + ContextAnnotated &annot_; // expected-note {{escapes to this field}} +}; + +struct FieldTypes { + int val_; + int *ptr_; + int &ref_; + std::string str_; + std::string_view view_; + + // Loans to fields (depend on enclosing object, not field origin). + int &get_val_ref() { // expected-warning {{implicit this in intra-TU function should be marked [[clang::lifetimebound]]}} + return val_; // expected-note {{param returned here}} + } + int *get_val_ptr() { // expected-warning {{implicit this in intra-TU function should be marked [[clang::lifetimebound]]}} + return &val_; // expected-note {{param returned here}} + } + std::string_view get_str_view() const { // expected-warning {{implicit this in intra-TU function should be marked [[clang::lifetimebound]]}} + return str_; // expected-note {{param returned here}} + } + int *&get_ptr_ref() { // expected-warning {{implicit this in intra-TU function should be marked [[clang::lifetimebound]]}} + return ptr_; // expected-note {{param returned here}} + } + + // Origins of fields -> field-origin-escape warning, no lifetimebound suggestion on 'this'. + int *get_ptr() const { // expected-warning {{return value depends on the lifetime of the object referenced by field 'ptr_', which cannot be expressed with [[clang::lifetimebound]]}} + return ptr_; // expected-note {{returned here}} + } + int &get_ptr_deref() const { // expected-warning {{return value depends on the lifetime of the object referenced by field 'ptr_', which cannot be expressed with [[clang::lifetimebound]]}} + return *ptr_; // expected-note {{returned here}} + } + int &get_ref() const { // expected-warning {{return value depends on the lifetime of the object referenced by field 'ref_', which cannot be expressed with [[clang::lifetimebound]]}} + return ref_; // expected-note {{returned here}} + } + int *get_ref_addr() const { // expected-warning {{return value depends on the lifetime of the object referenced by field 'ref_', which cannot be expressed with [[clang::lifetimebound]]}} + return &ref_; // expected-note {{returned here}} + } + std::string_view get_view() const { // expected-warning {{return value depends on the lifetime of the object referenced by field 'view_', which cannot be expressed with [[clang::lifetimebound]]}} + return view_; // expected-note {{returned here}} + } + const char *get_view_data() const { // expected-warning {{return value depends on the lifetime of the object referenced by field 'view_', which cannot be expressed with [[clang::lifetimebound]]}} + return view_.data(); // expected-note {{returned here}} + } +}; + +} // namespace field_origin_escape _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
