https://github.com/aeft updated https://github.com/llvm/llvm-project/pull/195603
>From 0b17f4fe14f4e99606a9008a9bf66be81edc4ca8 Mon Sep 17 00:00:00 2001 From: Zhijie Wang <[email protected]> Date: Wed, 3 Jun 2026 21:45:13 -0700 Subject: [PATCH] [LifetimeSafety] Track per-field origins for record types --- .../Analyses/LifetimeSafety/FactsGenerator.h | 2 - .../Analyses/LifetimeSafety/Origins.h | 30 ++ .../LifetimeSafety/FactsGenerator.cpp | 106 ++++-- clang/lib/Analysis/LifetimeSafety/Origins.cpp | 113 +++++- .../warn-lifetime-safety-dangling-field.cpp | 10 +- clang/test/Sema/warn-lifetime-safety.cpp | 353 +++++++++++++++++- 6 files changed, 557 insertions(+), 57 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index e08f039691f0e..c1ab8b45b26a8 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -141,8 +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); - bool escapesViaReturn(OriginID OID) const; llvm::SmallVector<Fact *> issuePlaceholderLoans(); diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h index 5006914dc5123..a5d4e8dea79e6 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h @@ -148,6 +148,20 @@ class OriginNode { return nullptr; } + /// To reach the record, peels the base's outer origin when the + /// base is a glvalue (`IsGLValue`) and one more level for an arrow access + /// (`IsArrow`), then looks up `FD`. Returns null if `FD` is not reachable. + OriginNode *resolveMemberField(const FieldDecl *FD, bool IsGLValue, + bool IsArrow) const { + assert(FD); + const OriginNode *N = this; + if (IsGLValue) + N = N->getPointeeChild(); + if (IsArrow && N) + N = N->getPointeeChild(); + return N ? N->getFieldChild(FD) : nullptr; + } + void setChildren(llvm::ArrayRef<Edge> NewChildren) { assert(Children.empty() && "children must be set at most once"); Children = NewChildren; @@ -231,9 +245,18 @@ 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. + bool isTrackedField(const FieldDecl *FD) const; void initializeThisOrigins(const Decl *D); @@ -262,6 +285,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 6c61ef3c2cf0e..ac6d95c6dbadf 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -277,12 +277,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. @@ -290,6 +294,29 @@ 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->resolveMemberField(FD, ME->getBase()->isGLValue(), ME->isArrow())) { + // 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) { @@ -399,18 +426,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); @@ -421,28 +454,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 @@ -585,9 +616,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)); } @@ -886,9 +922,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); @@ -1110,11 +1147,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..a05e91e710df6 100644 --- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp @@ -122,16 +122,39 @@ 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())) + // A loan only enters a field by being stored through it, which counts as an + // access. So a non-public field that is never accessed here, such as a + // type's private implementation pointer (e.g. inside std::string), can hold + // no loan we could miss, and we skip it. I.e., std::string gets no origin, + // while std::string* gets a single origin. + // + // A record can also be used as a whole (e.g. `s.inner`) while none of its + // own fields (e.g. `s.inner.v`) are accessed. Narrowing that use still + // needs the record to have origins (otherwise the use also keeps a sibling + // like `s.v` live), so a public field with origins provides them even when + // unaccessed. + if ((FD->getAccess() == AS_public || isAccessedField(FD)) && + hasOrigins(FD->getType())) return true; return false; } +bool OriginManager::isTrackedField(const FieldDecl *FD) const { + return isAccessedField(FD) && hasOrigins(FD->getType()); +} + /// Determines if an expression has origins that need to be tracked. /// /// An expression has origins if: @@ -208,9 +231,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 +269,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(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 +355,32 @@ 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())) { + const Expr *BaseE = ME->getBase()->IgnoreParenImpCasts(); + if (OriginNode *Base = getOrCreateNode(BaseE)) + if (OriginNode *Sub = Base->resolveMemberField(FD, BaseE->isGLValue(), + ME->isArrow())) { + // 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. @@ -336,9 +433,11 @@ void OriginManager::runPreScan(const AnalysisDeclContext &AC) { if (Expr *InitE = Init->getInit()) Collector.TraverseStmt(InitE); } + // hasOrigins consults AccessedFields, so populate it before registering + // lifetime-annotated types. + AccessedFields.insert_range(Collector.getAccessedFields()); for (QualType QT : Collector.getCollectedTypes()) registerLifetimeAnnotatedOriginType(QT); - AccessedFields.insert_range(Collector.getAccessedFields()); } void OriginManager::registerLifetimeAnnotatedOriginType(QualType QT) { 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..7b5cd740257e0 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,348 @@ 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(); +public: + void dangle_via_other(PrivateHolder &other) { + { + MyObj obj; + other.v = obj; // expected-warning {{local variable 'obj' does not live long enough}} + } // expected-note {{destroyed here}} + other.v.use(); // expected-note {{later used here}} + } + + PrivateHolder return_via_other() { + PrivateHolder result; + MyObj local; + result.v = local; // expected-warning {{stack memory associated with local variable 'local' is returned}} + return result; // expected-note {{returned here}} + } +}; + +PrivateHolder return_private_holder() { + PrivateHolder h; + MyObj local; + h.v = local; // expected-warning {{stack memory associated with local variable 'local' is returned}} + return h; // expected-note {{returned here}} +} + +class ProtectedField { +protected: + View v; +}; + +class DerivedAccess : public ProtectedField { +public: + // FIXME: False negative. `other.v` is inherited from `ProtectedField`; like + // `inherited_field`, the tree carries no edge for an inherited field, so the + // loan does not propagate. + void dangle_via_other(DerivedAccess &other) { + { + MyObj obj; + other.v = obj; + } + other.v.use(); + } +}; + +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 _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
