[llvm-branch-commits] [clang] [LifetimeSafety] Track per-field origins for record types (PR #195603)
Zhijie Wang via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Jun 4 08:46:08 PDT 2026
https://github.com/aeft updated https://github.com/llvm/llvm-project/pull/195603
>From f9a408c6c93750d865ac73b076c791b7f1d07a28 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Wed, 3 Jun 2026 21:45:13 -0700
Subject: [PATCH] [LifetimeSafety] Track per-field origins for record types
---
.../Analyses/LifetimeSafety/FactsGenerator.h | 1 -
.../Analyses/LifetimeSafety/Origins.h | 17 +
.../LifetimeSafety/FactsGenerator.cpp | 105 ++++--
clang/lib/Analysis/LifetimeSafety/Origins.cpp | 97 +++++-
.../warn-lifetime-safety-dangling-field.cpp | 10 +-
clang/test/Sema/warn-lifetime-safety.cpp | 320 +++++++++++++++++-
6 files changed, 495 insertions(+), 55 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index e97322cf04604..b050463c8242c 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -141,7 +141,6 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
// (e.g. on the left-hand side of an assignment in the case of a DeclRefExpr).
void handleUse(const Expr *E);
- void markUseAsWrite(const DeclRefExpr *DRE);
/// Walks the full subtree so origins on the pointee chain and on field
/// children both escape with the returned value.
void emitReturnEscapes(OriginNode *N, const Expr *RetExpr);
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
index 14309b549a3f3..39bd28f9433ee 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
@@ -221,9 +221,19 @@ class OriginManager {
OriginNode *createNode(const Expr *E, QualType QT);
void attachPointeeChild(OriginNode *Parent, OriginNode *Pointee);
+ void attachChildren(OriginNode *Parent,
+ llvm::ArrayRef<OriginNode::Edge> Children);
template <typename T>
OriginNode *buildNodeForType(QualType QT, const T *Node);
+ template <typename T>
+ OriginNode *buildNodeForTypeImpl(QualType QT, const T *Node,
+ llvm::SmallPtrSet<const Type *, 4> &Visited,
+ unsigned FieldDepth);
+
+ /// Whether a record field participates in origin tracking. Plain records
+ /// only track public fields; lambdas track all fields.
+ bool isTrackedField(const CXXRecordDecl *RD, const FieldDecl *FD) const;
void initializeThisOrigins(const Decl *D);
@@ -252,6 +262,13 @@ class OriginManager {
/// Fields accessed in the function body (or constructor init lists).
/// Fields outside this set are excluded from origin tracking.
llvm::SmallPtrSet<const FieldDecl *, 8> AccessedFields;
+
+ /// Field-edge depth limit when building origin trees for record types:
+ /// - `std::nullopt`: no limit (full field tree).
+ /// - `0`: disable field tracking (records become single-origin).
+ /// - `N > 0`: track up to N levels of field edges.
+ /// Pointee edges are not subject to this limit.
+ std::optional<size_t> MaxFieldDepth = std::nullopt;
};
} // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index f6d556a04672f..02a24e1333ca0 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -272,12 +272,16 @@ void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
}
void FactsGenerator::VisitMemberExpr(const MemberExpr *ME) {
- auto *MD = ME->getMemberDecl();
- if (isa<FieldDecl>(MD) && doesDeclHaveStorage(MD)) {
- assert(ME->isGLValue() && "Field member should be GL value");
- OriginNode *Dst = getOriginNode(*ME);
- assert(Dst && "Field member should have an origin list as it is GL value");
- OriginNode *Src = getOriginNode(*ME->getBase());
+ auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl());
+ if (!FD)
+ return;
+
+ assert(ME->isGLValue() && "Field member should be GL value");
+ OriginNode *Dst = getOriginNode(*ME);
+ assert(Dst && "Field member should have an origin list as it is GL value");
+
+ OriginNode *Src = getOriginNode(*ME->getBase());
+ if (doesDeclHaveStorage(FD)) {
assert(Src && "Base expression should be a pointer/reference type");
// The field's glvalue (outermost origin) holds the same loans as the base
// expression.
@@ -285,6 +289,28 @@ void FactsGenerator::VisitMemberExpr(const MemberExpr *ME) {
Dst->getOriginID(), Src->getOriginID(),
/*Kill=*/true));
}
+
+ // Only narrow when the field is in the base's tree; otherwise the
+ // MemberExpr resolved to an orphan disconnected from the base (e.g., a
+ // `[[gsl::Pointer]]` base whose fields aren't expanded), and narrowing
+ // would drop the base's root liveness, so a loan deposited on the root
+ // via `lifetime_capture_by(this)` would be missed.
+ if (Src && Src->getFieldChildInChain(FD)) {
+ // Narrow the UseFact's liveness coverage to the accessed field's
+ // subtree.
+ //
+ // E.g., for `(void)s.inner`, without narrowing, the UseFact at `s`
+ // would keep `s.v`'s subtree live and falsely flag a UAF when a loan
+ // held by `s.v` has already expired.
+ if (UseFact *UF = UseFacts.lookup(ME->getBase())) {
+ assert(!UseFacts.contains(ME) && "ME already has a UseFact");
+ OriginNode *NewUsedOrigins =
+ doesDeclHaveStorage(FD) ? Dst->getPointeeChild() : Dst;
+ UF->setUsedOrigins(NewUsedOrigins);
+ UseFacts[ME] = UF;
+ UseFacts.erase(ME->getBase());
+ }
+ }
}
void FactsGenerator::VisitCallExpr(const CallExpr *CE) {
@@ -395,18 +421,24 @@ void FactsGenerator::handleAssignment(const Expr *TargetExpr,
const Expr *RHSExpr) {
LHSExpr = LHSExpr->IgnoreParenImpCasts();
OriginNode *LHSNode = nullptr;
+ QualType LHSType;
+ UseFact *LHSUseFact = nullptr;
if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
LHSNode = getOriginNode(*DRE_LHS);
assert(LHSNode && "LHS is a DRE and should have an origin list");
- }
- // Handle assignment to member fields (e.g., `this->view = s` or `view = s`).
- // This enables detection of dangling fields when local values escape to
- // fields.
- if (const auto *ME_LHS = dyn_cast<MemberExpr>(LHSExpr)) {
+ LHSType = DRE_LHS->getDecl()->getType();
+ LHSUseFact = UseFacts.lookup(DRE_LHS);
+ } else if (const auto *ME_LHS = dyn_cast<MemberExpr>(LHSExpr)) {
+ // Handle assignment to member fields (e.g., `this->view = s` or `view =
+ // s`). This enables detection of dangling fields when local values escape
+ // to fields.
LHSNode = getOriginNode(*ME_LHS);
assert(LHSNode && "LHS is a MemberExpr and should have an origin list");
+ LHSType = ME_LHS->getMemberDecl()->getType();
+ LHSUseFact = UseFacts.lookup(ME_LHS);
}
+
if (!LHSNode)
return;
OriginNode *RHSNode = getOriginNode(*RHSExpr);
@@ -417,28 +449,26 @@ void FactsGenerator::handleAssignment(const Expr *TargetExpr,
// assigned.
RHSNode = getRValueOrigins(RHSExpr, RHSNode);
- if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
- QualType QT = DRE_LHS->getDecl()->getType();
- if (QT->isReferenceType()) {
- if (hasOrigins(QT->getPointeeType())) {
+ if (LHSUseFact) {
+ if (LHSType->isReferenceType()) {
+ if (hasOrigins(LHSType->getPointeeType())) {
// Writing through a reference uses the binding but overwrites the
// pointee. Model this as a Read of the outer origin (keeping the
// binding live) and a Write of the inner origins (killing the pointee's
// liveness).
- if (UseFact *UF = UseFacts.lookup(DRE_LHS)) {
- const OriginNode *FullNode = UF->getUsedOrigins();
- assert(FullNode);
- UF->setUsedOrigins(FactMgr.getOriginMgr().createSingleOriginNode(
- FullNode->getOriginID()));
- if (const OriginNode *InnerNode = FullNode->getPointeeChild()) {
- UseFact *WriteUF = FactMgr.createFact<UseFact>(DRE_LHS, InnerNode);
- WriteUF->markAsWritten();
- CurrentBlockFacts.push_back(WriteUF);
- }
+ const OriginNode *FullNode = LHSUseFact->getUsedOrigins();
+ assert(FullNode);
+ LHSUseFact->setUsedOrigins(
+ FactMgr.getOriginMgr().createSingleOriginNode(
+ FullNode->getOriginID()));
+ if (const OriginNode *InnerNode = FullNode->getPointeeChild()) {
+ UseFact *WriteUF = FactMgr.createFact<UseFact>(LHSExpr, InnerNode);
+ WriteUF->markAsWritten();
+ CurrentBlockFacts.push_back(WriteUF);
}
}
} else
- markUseAsWrite(DRE_LHS);
+ LHSUseFact->markAsWritten();
}
if (!RHSNode) {
// RHS has no tracked origins (e.g., assigning a callable without origins
@@ -581,9 +611,14 @@ void FactsGenerator::VisitCXXFunctionalCastExpr(
void FactsGenerator::VisitInitListExpr(const InitListExpr *ILE) {
if (!hasOrigins(ILE))
return;
- // For list initialization with a single element, like `View{...}`, the
- // origin of the list itself is the origin of its single element.
- if (ILE->getNumInits() == 1)
+ // For list initialization with a single element of the same type, like
+ // `View{other}`, the origin of the list itself is the origin of its single
+ // element.
+ //
+ // TODO: Handle aggregate (record/array) list initialization.
+ if (ILE->getNumInits() == 1 &&
+ ILE->getType().getCanonicalType() ==
+ ILE->getInit(0)->getType().getCanonicalType())
killAndFlowOrigin(*ILE, *ILE->getInit(0));
}
@@ -882,9 +917,10 @@ void FactsGenerator::handleImplicitObjectFieldUses(const Expr *Call,
const auto UseFields = [&](const CXXRecordDecl *RD) {
for (const auto *Field : RD->fields())
- if (auto *FieldNode = getOriginNode(*Field))
- CurrentBlockFacts.push_back(
- FactMgr.createFact<UseFact>(Call, FieldNode));
+ if (FactMgr.getOriginMgr().isAccessedField(Field))
+ if (auto *FieldNode = getOriginNode(*Field))
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<UseFact>(Call, FieldNode));
};
UseFields(ClassDecl);
@@ -1106,11 +1142,6 @@ void FactsGenerator::handleUse(const Expr *E) {
}
}
-void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) {
- if (UseFacts.contains(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::issuePlaceholderLoans() {
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 510232f5dc983..afab984390158 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -122,16 +122,29 @@ bool OriginManager::hasOrigins(QualType QT) const {
// stored lambda's origins.
if (isStdCallableWrapperType(RD))
return true;
- // TODO: Limit to lambdas for now. This will be extended to user-defined
- // structs with pointer-like fields.
- if (!RD->isLambda())
+ // A lambda has origins when any capture has a tracked type; the lambda
+ // itself is tracked as a single origin.
+ if (RD->isLambda()) {
+ for (const auto *FD : RD->fields())
+ if (hasOrigins(FD->getType()))
+ return true;
+ return false;
+ }
+ // TODO: Unions are not tracked.
+ if (RD->isUnion())
return false;
for (const auto *FD : RD->fields())
- if (hasOrigins(FD->getType()))
+ if (FD->getAccess() == AS_public && hasOrigins(FD->getType()))
return true;
return false;
}
+bool OriginManager::isTrackedField(const CXXRecordDecl *RD,
+ const FieldDecl *FD) const {
+ return FD->getAccess() == AS_public && hasOrigins(FD->getType()) &&
+ isAccessedField(FD);
+}
+
/// Determines if an expression has origins that need to be tracked.
///
/// An expression has origins if:
@@ -208,9 +221,37 @@ void OriginManager::attachPointeeChild(OriginNode *Parent,
Parent->setChildren({E, 1});
}
+void OriginManager::attachChildren(OriginNode *Parent,
+ llvm::ArrayRef<OriginNode::Edge> Children) {
+ Parent->setChildren(Children.copy(Allocator));
+}
+
template <typename T>
OriginNode *OriginManager::buildNodeForType(QualType QT, const T *Node) {
- assert(hasOrigins(QT) && "buildNodeForType called for non-pointer type");
+ llvm::SmallPtrSet<const Type *, 4> Visited;
+ return buildNodeForTypeImpl(QT, Node, Visited, 0);
+}
+
+template <typename T>
+OriginNode *
+OriginManager::buildNodeForTypeImpl(QualType QT, const T *Node,
+ llvm::SmallPtrSet<const Type *, 4> &Visited,
+ unsigned FieldDepth) {
+ assert(hasOrigins(QT) && "buildNodeForType called for type without origins");
+
+ const auto *RD = QT->getAsCXXRecordDecl();
+ const Type *Canonical = QT.getCanonicalType().getTypePtr();
+ // Cycle cut: only records enter Visited; re-entering one returns a
+ // leaf to stop descending further. Loans landing on the cut leaf are
+ // dropped (e.g., through `n->next->next`).
+ //
+ // Pointer/reference types stay transparent: including them in Visited
+ // would make the same record's shape depend on the entry path. E.g.,
+ // Node's Sub_next would have length 2 from a Node start but length 1
+ // from a Node* start, breaking flow's length assertion.
+ if (RD && !Visited.insert(Canonical).second)
+ return createNode(Node, QT);
+
OriginNode *Head = createNode(Node, QT);
if (QT->isPointerOrReferenceType()) {
@@ -218,8 +259,28 @@ OriginNode *OriginManager::buildNodeForType(QualType QT, const T *Node) {
// We recurse if the pointee type is pointer-like, to build the next
// level in the origin tree. E.g., for T*& / View&.
if (hasOrigins(PointeeTy))
- attachPointeeChild(Head, buildNodeForType(PointeeTy, Node));
+ attachPointeeChild(
+ Head, buildNodeForTypeImpl(PointeeTy, Node, Visited, FieldDepth));
+ } else if (RD) {
+ bool WithinFieldDepthLimit = !MaxFieldDepth || FieldDepth < *MaxFieldDepth;
+ bool shouldExpandFields =
+ !(isGslPointerType(QT) || isStdCallableWrapperType(RD) ||
+ LifetimeAnnotatedOriginTypes.contains(Canonical) || RD->isLambda()) &&
+ WithinFieldDepthLimit;
+ if (shouldExpandFields) {
+ SmallVector<OriginNode::Edge, 4> FieldChildren;
+ for (const FieldDecl *F : RD->fields())
+ if (isTrackedField(RD, F)) {
+ OriginNode *Sub =
+ buildNodeForTypeImpl(F->getType(), Node, Visited, FieldDepth + 1);
+ FieldChildren.push_back({F, Sub});
+ }
+ attachChildren(Head, FieldChildren);
+ }
}
+
+ if (RD)
+ Visited.erase(Canonical);
return Head;
}
@@ -284,6 +345,30 @@ OriginNode *OriginManager::getOrCreateNode(const Expr *E) {
return ExprToNode[E] = Head;
}
+ // For a MemberExpr whose base is not `this` (handled above), look up the
+ // field child in the base's per-instance origin tree. This makes loans
+ // flowing into one occurrence of `s.v` visible at later occurrences.
+ if (auto *ME = dyn_cast<MemberExpr>(E))
+ if (auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl()))
+ if (OriginNode *Base =
+ getOrCreateNode(ME->getBase()->IgnoreParenImpCasts()))
+ if (OriginNode *Sub = Base->getFieldChildInChain(FD)) {
+ // For non-reference fields (e.g., `View v;` in a record), the
+ // MemberExpr `s.v` is an lvalue (addressable) that can be
+ // borrowed, so we create an outer origin for the lvalue itself,
+ // with the pointee being the field's shared subtree. `&s.v` borrows
+ // the storage of the v-slot in s, not what v refers to.
+ if (doesDeclHaveStorage(FD)) {
+ OriginNode *Outer = createNode(E, QualType{});
+ attachPointeeChild(Outer, Sub);
+ return ExprToNode[E] = Outer;
+ }
+ // For reference-typed fields (e.g., `int& r;` in a record) which
+ // have no storage, the MemberExpr `s.r` directly reuses the
+ // field's subtree.
+ return ExprToNode[E] = Sub;
+ }
+
// If E is an lvalue , it refers to storage. We model this storage as the
// first level of origin list, as if it were a reference, because l-values are
// addressable.
diff --git a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
index cb13f51b41355..af6d933bbed97 100644
--- a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
@@ -162,27 +162,25 @@ struct MemberSetters {
}
};
-// FIXME: Detect escape to field of field.
struct IndirectEscape{
struct {
const char *p;
- } b;
+ } b; // expected-note {{this field dangles}}
void foo() {
std::string s;
- b.p = s.data();
+ b.p = s.data(); // expected-warning {{stack memory associated with local variable 's' escapes to the field 'b' which will dangle}}
}
};
-// FIXME: Detect escape to field of field.
struct IndirectEscape2 {
- struct {
+ struct { // expected-note {{this field dangles}}
const char *p;
};
void foo() {
std::string s;
- p = s.data();
+ p = s.data(); // expected-warning {{stack memory associated with local variable 's' escapes to the field}}
}
};
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 5aaa389b63ccd..6378027e410b4 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -3190,14 +3190,13 @@ struct T {
}
};
-// FIXME: false-negative
void foo() {
S s;
{
int num;
- s.p_ = # // does not warn
- }
- s.bar();
+ s.p_ = # // expected-warning {{local variable 'num' does not live long enough}}
+ } // expected-note {{destroyed here}}
+ s.bar(); // expected-note {{later used here}}
s.p_ = &GLOBAL_INT;
}
@@ -3594,4 +3593,315 @@ void capturing_multiple_locals() {
setCaptureBy(v, local2); // expected-warning{{local variable 'local2' does not live long enough}}
} // expected-note 2 {{destroyed here}}
(void)v; // expected-note 2 {{later used here}}
-}
\ No newline at end of file
+}
+
+namespace tree_origin {
+
+struct Inner {
+ View v;
+ View w;
+};
+
+struct S {
+ View v;
+ Inner inner;
+ int x;
+};
+
+void struct_field() {
+ S s;
+ {
+ MyObj obj;
+ s.v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
+ } // expected-note {{destroyed here}}
+ s.v.use(); // expected-note {{later used here}}
+}
+
+void struct_field2() {
+ S s;
+ {
+ MyObj obj;
+ s.inner.v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)s.inner; // expected-note {{later used here}}
+}
+
+void struct_field3() {
+ S s;
+ {
+ MyObj obj;
+ s.inner.v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
+ } // expected-note {{destroyed here}}
+ s.inner.v.use(); // expected-note {{later used here}}
+}
+
+void struct_field_safe() {
+ S s;
+ {
+ MyObj obj;
+ s.v = obj;
+ }
+ (void)s.inner;
+}
+
+void struct_field_safe2() {
+ S s;
+ {
+ MyObj obj;
+ s.inner.v = obj;
+ }
+ (void)s.inner.w;
+}
+
+S return_struct() {
+ S s;
+ MyObj local;
+ s.v = local; // expected-warning {{stack memory associated with local variable 'local' is returned}}
+ return s; // expected-note {{returned here}}
+}
+
+S return_struct_assign() {
+ S s, s2;
+ MyObj local;
+ s.v = local; // expected-warning {{stack memory associated with local variable 'local' is returned}}
+ s2 = s;
+ return s2; // expected-note {{returned here}}
+}
+
+View *return_field_addr() {
+ S s;
+ View* v = &s.v; // expected-warning {{stack memory associated with local variable 's' is returned}}
+ return v; // expected-note {{returned here}}
+}
+
+void struct_field_in_loop(int n) {
+ S s;
+ for (int i = 0; i < n; i++) {
+ MyObj obj;
+ s.v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)s; // expected-note {{later used here}}
+}
+
+void struct_field_in_loop_safe(int n) {
+ for (int i = 0; i < n; i++) {
+ MyObj obj;
+ S s;
+ s.v = obj;
+ }
+}
+
+void struct_field_in_loop_safe2(int n) {
+ S s;
+ for (int i = 0; i < n; i++) {
+ MyObj obj;
+ s.v = obj;
+ }
+}
+
+void struct_field_read_in_loop_safe(int n) {
+ for (int i = 0; i < n; i++) {
+ S s;
+ (void)s.v;
+ }
+}
+
+void struct_int_field_read_in_loop_safe(int n) {
+ for (int i = 0; i < n; i++) {
+ S s;
+ (void)s.x;
+ }
+}
+
+void field_pointee_propagate_in_loop_safe(int n) {
+ View outer;
+ MyObj obj;
+ for (int i = 0; i < n; i++) {
+ S local;
+ local.v = obj;
+ outer = local.v;
+ }
+ outer.use();
+}
+
+struct PtrRef {
+ MyObj *&ref;
+};
+
+void ref_field_dangle() {
+ MyObj *p;
+ PtrRef h{p};
+ {
+ MyObj temp;
+ h.ref = &temp; // expected-warning {{local variable 'temp' does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)*h.ref; // expected-note {{later used here}}
+}
+
+void ref_field_dangle_then_rescue() {
+ MyObj safe;
+ MyObj *p;
+ PtrRef h{p};
+ {
+ MyObj temp;
+ h.ref = &temp;
+ }
+ h.ref = &safe;
+ (void)*h.ref;
+}
+
+struct Node {
+ Node *next;
+ View v;
+};
+
+void self_referential_struct_field() {
+ Node n;
+ {
+ MyObj obj;
+ n.v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
+ } // expected-note {{destroyed here}}
+ n.v.use(); // expected-note {{later used here}}
+}
+
+void self_referential_pointer_assign() {
+ Node n;
+ {
+ Node child;
+ n.next = &child; // expected-warning {{local variable 'child' does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)n; // expected-note {{later used here}}
+}
+
+struct SelfRefMultiPtr {
+ SelfRefMultiPtr *p1;
+ SelfRefMultiPtr **p2;
+};
+
+void self_referential_mixed_pointer_depth() {
+ SelfRefMultiPtr s;
+ {
+ SelfRefMultiPtr *p;
+ s.p2 = &p; // expected-warning {{local variable 'p' does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)s; // expected-note {{later used here}}
+}
+
+// FIXME: False negative. The cycle-cut leaf at `n.next`'s pointee has
+// no field edges, so the loan on `n.next->v` lands on an orphan tree
+// disconnected from n.
+void self_referential_pointer_field_write() {
+ Node n, child;
+ n.next = &child;
+ {
+ MyObj local;
+ n.next->v = local;
+ }
+ (void)n; // Should warn.
+}
+
+struct DerivedView : S {
+ View v2;
+};
+
+void derived_to_base_upcast() {
+ DerivedView d;
+ // CK_DerivedToBase: Src and Dst children sets diverge in flow (Base and
+ // Derived have different direct fields), so flow must skip unmatched
+ // fields without crashing.
+ S& b = d;
+ (void)b;
+}
+
+// FIXME: False positive.
+void derived_field_safe() {
+ DerivedView d;
+ {
+ MyObj obj;
+ d.v2 = obj; // expected-warning {{local variable 'obj' does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)d.v; // expected-note {{later used here}}
+}
+
+class PrivateHolder {
+private:
+ View v;
+ friend PrivateHolder return_private_holder();
+};
+
+// FIXME: False negative. Don't track origins for private fields for now.
+PrivateHolder return_private_holder() {
+ PrivateHolder h;
+ MyObj local;
+ h.v = local;
+ return h; // Should warn.
+}
+
+struct UnionStruct {
+ union {
+ int* p;
+ float f;
+ };
+};
+
+// FIXME: False negative. Don't track origins for union for now.
+UnionStruct union_field() {
+ UnionStruct u;
+ int x;
+ u.p = &x;
+ return u; // Should warn.
+}
+
+struct StoreS {
+ View v;
+ void store(const MyObj& obj) { v = obj; }
+};
+
+// FIXME: False negative. Two pieces missing:
+// 1. Infer `lifetime_capture_by(this)` on non-ctor methods that
+// capture a parameter into a field.
+// 2. Consume the attribute in `handleFunctionCall` and flow the
+// captured argument's origin into the corresponding target at
+// the call site.
+StoreS return_after_this_set() {
+ StoreS s;
+ MyObj local;
+ s.store(local);
+ return s; // Should warn.
+}
+
+struct BaseS { View v; };
+struct DerivedS : BaseS {};
+
+// FIXME: False negative. `d.v` accesses an inherited field. Only track
+// direct fields for now, so DerivedS's tree has no edge for `v` and the
+// loan does not propagate.
+DerivedS inherited_field() {
+ DerivedS d;
+ MyObj local;
+ d.v = local;
+ return d; // Should warn.
+}
+
+// FIXME: False negative. `p->v1 = local` deposits into p's origin tree,
+// which is independent of s's tree after the initial `p = &s` flow.
+// Requires alias analysis.
+S field_write_via_pointer() {
+ S s;
+ MyObj local;
+ S *p = &s;
+ p->v = local;
+ return s; // Should warn.
+}
+
+struct ViewPtrHolder { View *p; };
+
+// FIXME: False negative. Aggregate (record/array) list initialization is
+// not handled, so the loan from `&v` does not flow into `h.p`.
+View *struct_init_single_field() {
+ View v;
+ ViewPtrHolder h{&v};
+ return h.p; // Should warn.
+}
+
+} // namespace tree_origin
More information about the llvm-branch-commits
mailing list