[clang] 63dcd11 - [LifetimeSafety] Add warning for member functions returning field origins (#229784)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Oct 14 03:35:28 PDT 2026
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
More information about the cfe-commits
mailing list