Author: Utkarsh Saxena
Date: 2026-10-14T10:35:23Z
New Revision: 63dcd11b2cc063f209605b8960ec12e3c29f1a75

URL: 
https://github.com/llvm/llvm-project/commit/63dcd11b2cc063f209605b8960ec12e3c29f1a75
DIFF: 
https://github.com/llvm/llvm-project/commit/63dcd11b2cc063f209605b8960ec12e3c29f1a75.diff

LOG: [LifetimeSafety] Add warning for member functions returning field origins 
(#229784)

This patch introduces an opt-in 
`-Wexperimental-lifetime-safety-field-origin-escape`
diagnostic in Lifetime Safety to detect non-constructor instance methods
whose return value depends on the origin of a reference, pointer, or
view field rather than the lifetime of the enclosing object (`*this`).
To support this, placeholder loans are issued for origin-carrying fields
(`$this.field`) during analysis of instance methods, distinguishing
between returning a borrow of a field's storage (which depends on
`*this` and warrants `[[clang::lifetimebound]]`) and returning a value
derived from a field's origin (where the returned reference or view
outlives the wrapper object and instead depends on the underlying
referent).

This diagnostic helps identify wrapper/view methods where
`[[clang::lifetimebound]]` cannot express the true lifetime
contract—annotating such methods with `[[clang::lifetimebound]]` would
cause false-positive use-after-scope warnings when a temporary wrapper
is destroyed while its underlying referent remains alive, whereas
leaving them unannotated risks false negatives when both expire. Because
a raw pointer field may represent owned storage tied to `*this` (e.g.,
`Vector::data()`) rather than a non-owning reference (e.g.,
`Span::data()`), the warning is suppressed if the method is already
marked `[[clang::lifetimebound]]`, and an accompanying note suggests
marking the method `[[clang::lifetimebound]]` when the escaping field is
a pointer owned by the class.

Added: 
    

Modified: 
    clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
    clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
    clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
    clang/include/clang/Basic/DiagnosticGroups.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/Analysis/LifetimeSafety/Checker.cpp
    clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
    clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
    clang/lib/Analysis/LifetimeSafety/Loans.cpp
    clang/lib/Sema/SemaLifetimeSafety.h
    clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp

Removed: 
    


################################################################################
diff  --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index b20da9b48ed57d..f5f84f27b5035f 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -17,6 +17,7 @@
 
 #include "clang/AST/StmtVisitor.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
 #include "clang/Analysis/AnalysisDeclContext.h"
 #include "clang/Analysis/CFG.h"
@@ -28,8 +29,9 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
   using Base = ConstStmtVisitor<FactsGenerator>;
 
 public:
-  FactsGenerator(FactManager &FactMgr, AnalysisDeclContext &AC)
-      : FactMgr(FactMgr), AC(AC),
+  FactsGenerator(FactManager &FactMgr, AnalysisDeclContext &AC,
+                 const LifetimeSafetyOpts &LSOpts)
+      : FactMgr(FactMgr), AC(AC), LSOpts(LSOpts),
         IsCMode(!AC.getASTContext().getLangOpts().CPlusPlus &&
                 !AC.getASTContext().getLangOpts().ObjC) {}
 
@@ -163,8 +165,11 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
   bool escapesViaReturn(OriginID OID) const;
 
   llvm::SmallVector<Fact *> issuePlaceholderLoans();
+  void issueFieldPlaceholderLoans(const CXXMethodDecl *MD,
+                                  llvm::SmallVectorImpl<Fact *> &Facts);
   FactManager &FactMgr;
   AnalysisDeclContext &AC;
+  const LifetimeSafetyOpts &LSOpts;
   llvm::SmallVector<Fact *> CurrentBlockFacts;
   // Collect origins that escape the function in this block 
(OriginEscapesFact),
   // appended at the end of CurrentBlockFacts to ensure they appear after

diff  --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 6854902059e501..e6fbc2a5caee72 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 5643f0fc873148..8f8356444e4b86 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 dyn_cast<const ParmVarDecl *>(ParamOrMethod);
+    return dyn_cast<const ParmVarDecl *>(ParamMethodOrField);
   }
 
   const CXXMethodDecl *getImplicitThisParent() const {
-    return dyn_cast<const CXXMethodDecl *>(ParamOrMethod);
+    return dyn_cast<const CXXMethodDecl *>(ParamMethodOrField);
   }
 
-  using KeyTy = llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>;
+  const FieldDecl *getFieldDecl() const {
+    return dyn_cast<const FieldDecl *>(ParamMethodOrField);
+  }
+
+  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 aec64e8e76d6f9..b6c7ff7276260f 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -744,6 +744,16 @@ Validates correct usage and adherence to lifetime safety 
annotations like [[clan
   }];
 }
 
+def LifetimeSafetyFieldOriginEscape
+    : DiagGroup<"experimental-lifetime-safety-field-origin-escape"> {
+  code Documentation = [{
+Experimental. Detects member functions returning a value derived from a
+pointer, reference, or view field rather than from ``*this``. May report owned
+pointer fields (e.g., ``Vector::data()``), which can be silenced by marking the
+method ``[[clang::lifetimebound]]``.
+  }];
+}
+
 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 162106c84d120d..48b166d2a48a05 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11152,6 +11152,14 @@ def warn_lifetime_safety_noescape_escapes
       InGroup<LifetimeSafetyNoescape>,
       DefaultIgnore;
 
+def warn_lifetime_safety_field_origin_escape
+    : Warning<"return value depends on the object referenced by %0, not on 
'*this'">,
+      InGroup<LifetimeSafetyFieldOriginEscape>,
+      DefaultIgnore;
+
+def note_lifetime_safety_owned_field_suggestion
+    : Note<"if %0 is owned by the class, mark the method 
[[clang::lifetimebound]]">;
+
 // 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 fbf345460b6d85..3d2cab92b384bf 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,24 @@ class LifetimeChecker {
           AnnotationWarningsMap.try_emplace(MD, ReturnEsc->getReturnExpr());
       }
     };
+    // Identifies methods whose return value derives from a field's origin
+    // rather than *this, e.g., Span::data() or Vector::data().
+    auto CheckField = [&](const FieldDecl *Field) {
+      if (!LSOpts.CheckFieldOriginEscape)
+        return;
+      // Lifetimebound marks the field as owned by *this (e.g., 
Vector::data()).
+      const auto *MD = dyn_cast<CXXMethodDecl>(FD);
+      if (MD && implicitObjectParamIsLifetimeBound(MD))
+        return;
+      if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF)) {
+        // Ignore field loans in inner origins, e.g., returning a reference to
+        // a view field borrows *this.
+        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 +202,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 +469,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 d1113783ab882a..b67288a190f17d 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -1204,8 +1204,35 @@ void FactsGenerator::handleUse(const Expr *E) {
     CurrentBlockFacts.push_back(FactMgr.createFact<UseFact>(E, List));
 }
 
+// Issues a placeholder loan (this->field) for each origin-carrying field of 
the
+// class and its bases. It represents the storage the field refers to, as
+// opposed to the field itself (covered by $this).
+void FactsGenerator::issueFieldPlaceholderLoans(
+    const CXXMethodDecl *MD, llvm::SmallVectorImpl<Fact *> &Facts) {
+  const CXXRecordDecl *ClassDecl = MD->getParent()->getDefinition();
+  if (!ClassDecl)
+    return;
+  auto IssueForRecord = [&](const CXXRecordDecl *RD) {
+    for (const FieldDecl *Field : RD->fields()) {
+      // Skip private fields of bases as they cannot be named in MD.
+      if (RD != ClassDecl && Field->getAccess() == AS_private)
+        continue;
+      OriginList *FieldList = getOriginsList(*Field);
+      if (!FieldList)
+        continue;
+      const Loan *L = FactMgr.getLoanMgr().createPlaceholderLoan(Field);
+      Facts.push_back(FactMgr.createFact<IssueFact>(
+          L->getID(), FieldList->getOuterOriginID()));
+    }
+    return true;
+  };
+  IssueForRecord(ClassDecl);
+  ClassDecl->forallBases(IssueForRecord);
+}
+
 // Creates an IssueFact for a new placeholder loan for each pointer or 
reference
-// parameter at the function's entry.
+// parameter, the implicit 'this', and (if enabled) each origin-carrying field
+// at the function's entry.
 llvm::SmallVector<Fact *> FactsGenerator::issuePlaceholderLoans() {
   const auto *FD = dyn_cast<FunctionDecl>(AC.getDecl());
   if (!FD)
@@ -1214,10 +1241,12 @@ 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 (LSOpts.CheckFieldOriginEscape && !isa<CXXConstructorDecl>(MD))
+      issueFieldPlaceholderLoans(MD, PlaceholderLoanFacts);
   }
   for (const ParmVarDecl *PVD : FD->parameters()) {
     OriginList *List = getOriginsList(*PVD);

diff  --git a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp 
b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
index 0e1e4e28d1e59d..d28c55a8e60aa3 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
@@ -71,7 +71,7 @@ void LifetimeSafetyAnalysis::run() {
 
   FactMgr = std::make_unique<FactManager>(AC, Cfg);
 
-  FactsGenerator FactGen(*FactMgr, AC);
+  FactsGenerator FactGen(*FactMgr, AC, LSOpts);
   FactGen.run();
 
   /// TODO(opt): Consider optimizing individual blocks before running the

diff  --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp 
b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
index e6cbd922d20c72..c15f9b499b35b8 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 52ee8c0037b8d0..3db298b6fec85c 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,22 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
         << EscapeExpr->getSourceRange();
   }
 
+  void reportFieldOriginEscape(const CXXMethodDecl *MD, const FieldDecl *FD,
+                               const Expr *EscapeExpr) override {
+    std::string FieldDesc = getDiagSubjectDescription(FD);
+    S.Diag(MD->getLocation(), diag::warn_lifetime_safety_field_origin_escape)
+        << FieldDesc << MD->getNameInfo().getSourceRange();
+
+    S.Diag(EscapeExpr->getBeginLoc(), diag::note_lifetime_safety_returned_here)
+        << EscapeExpr->getSourceRange();
+
+    // A raw pointer field may own its storage (e.g., Vector::data()).
+    if (FD->getType()->isPointerType())
+      S.Diag(FD->getLocation(),
+             diag::note_lifetime_safety_owned_field_suggestion)
+          << FieldDesc << FD->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 4deb6d1f8688d6..34f3ad4f45de1a 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 
-Wexperimental-lifetime-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 -Wexperimental-lifetime-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 -Wexperimental-lifetime-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,147 @@ 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 
object referenced by field 'inner_', not on '*this'}}
+    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 object referenced by field 'inner_', not on '*this'}}
+    return inner_.method();         // expected-note {{returned here}}
+  }
+
+  std::string_view annot_method() const { // expected-warning {{return value 
depends on the object referenced by field 'annot_', not on '*this'}}
+    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_; // expected-note 2 {{if field 'ptr_' is owned by the class, mark 
the method [[clang::lifetimebound]]}}
+  int &ref_;
+  std::string str_;
+  std::string &str_ref_;
+  std::string *str_ptr_; // expected-note 2 {{if field 'str_ptr_' is owned by 
the class, mark the method [[clang::lifetimebound]]}}
+  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}}
+  }
+  const char *get_str_data() const { // expected-warning {{implicit this in 
intra-TU function should be marked [[clang::lifetimebound]]}}
+    return str_.data();              // expected-note {{param returned here}}
+  }
+  const char *get_str_c_str() const { // expected-warning {{implicit this in 
intra-TU function should be marked [[clang::lifetimebound]]}}
+    return str_.c_str();              // expected-note {{param returned here}}
+  }
+  const std::string &get_str_ref() 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}}
+  }
+
+  // Owned pointer field annotated with [[clang::lifetimebound]] -> no warning.
+  int *get_owned_ptr() const [[clang::lifetimebound]] {
+    return ptr_;
+  }
+
+  // Origins of fields -> field-origin-escape warning, no lifetimebound 
suggestion on 'this'.
+  int *get_ptr() const { // expected-warning {{return value depends on the 
object referenced by field 'ptr_', not on '*this'}}
+    return ptr_;         // expected-note {{returned here}}
+  }
+  int &get_ptr_deref() const { // expected-warning {{return value depends on 
the object referenced by field 'ptr_', not on '*this'}}
+    return *ptr_;              // expected-note {{returned here}}
+  }
+  int &get_ref() const { // expected-warning {{return value depends on the 
object referenced by field 'ref_', not on '*this'}}
+    return ref_;         // expected-note {{returned here}}
+  }
+  int *get_ref_addr() const { // expected-warning {{return value depends on 
the object referenced by field 'ref_', not on '*this'}}
+    return &ref_;             // expected-note {{returned here}}
+  }
+  std::string_view get_view() const { // expected-warning {{return value 
depends on the object referenced by field 'view_', not on '*this'}}
+    return view_;                     // expected-note {{returned here}}
+  }
+  const char *get_view_data() const { // expected-warning {{return value 
depends on the object referenced by field 'view_', not on '*this'}}
+    return view_.data();              // expected-note {{returned here}}
+  }
+  const char *get_str_ref_data() const { // expected-warning {{return value 
depends on the object referenced by field 'str_ref_', not on '*this'}}
+    return str_ref_.data();              // expected-note {{returned here}}
+  }
+  std::string_view get_str_ref_view() const { // expected-warning {{return 
value depends on the object referenced by field 'str_ref_', not on '*this'}}
+    return str_ref_;                          // expected-note {{returned 
here}}
+  }
+  const char *get_str_ptr_data() const { // expected-warning {{return value 
depends on the object referenced by field 'str_ptr_', not on '*this'}}
+    return str_ptr_->data();             // expected-note {{returned here}}
+  }
+  std::string_view get_str_ptr_view() const { // expected-warning {{return 
value depends on the object referenced by field 'str_ptr_', not on '*this'}}
+    return *str_ptr_;                         // expected-note {{returned 
here}}
+  }
+};
+
+struct Base {
+  int *pub_ptr_; // expected-note {{if field 'pub_ptr_' is owned by the class, 
mark the method [[clang::lifetimebound]]}}
+  int *get_priv() const { // expected-warning {{return value depends on the 
object referenced by field 'priv_ptr_', not on '*this'}}
+    return priv_ptr_;     // expected-note {{returned here}}
+  }
+
+protected:
+  std::string_view prot_view_;
+
+private:
+  int *priv_ptr_; // expected-note {{if field 'priv_ptr_' is owned by the 
class, mark the method [[clang::lifetimebound]]}}
+};
+
+struct Mid : virtual Base {};
+struct Derived : Mid, virtual Base {
+  int *get_pub() const { // expected-warning {{return value depends on the 
object referenced by field 'pub_ptr_', not on '*this'}}
+    return pub_ptr_;     // expected-note {{returned here}}
+  }
+  std::string_view get_prot() const { // expected-warning {{return value 
depends on the object referenced by field 'prot_view_', not on '*this'}}
+    return prot_view_;                // expected-note {{returned here}}
+  }
+  // Private base field reached via a call: no field-origin-escape warning.
+  int *get_priv_via_base() const { return get_priv(); }
+};
+
+} // namespace field_origin_escape


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

Reply via email to