https://github.com/Xazax-hun updated https://github.com/llvm/llvm-project/pull/204612
From a5198c9e68e98172eb1b8a245f5c46c573f9d2cf Mon Sep 17 00:00:00 2001 From: Gabor Horvath <[email protected]> Date: Thu, 18 Jun 2026 15:53:10 +0100 Subject: [PATCH] [LifetimeSafety] Model pointer-to-data-member access in the fact generator VisitBinaryOperator had no case for `obj.*pm` (BO_PtrMemD) / `objptr->*pm` (BO_PtrMemI), so a borrow of the accessed member (`&(obj.*pm)`) dropped the object's loan to an empty origin and a use-after-scope was missed. Flow the object operand's origin into the result, mirroring a member access: for `.*` the object is the LHS, for `->*` it is the LHS pointer's pointee. Assisted-by: Claude Opus 4.8 --- .../LifetimeSafety/FactsGenerator.cpp | 21 +++++++ clang/test/Sema/LifetimeSafety/safety.cpp | 60 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index d56703a4b29c4..395306d58f5bd 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -468,6 +468,27 @@ void FactsGenerator::handlePointerArithmetic(const BinaryOperator *BO) { } void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) { + if (BO->getOpcode() == BO_PtrMemD || BO->getOpcode() == BO_PtrMemI) { + // `obj.*pm` / `objptr->*pm` names a member of the object, so a borrow of it + // borrows the object; flow the object's origin into the result. For `.*` + // the object is the LHS; for `->*` it is the LHS pointer's pointee. + // + // Only the result's outer (storage) origin relates to the object: borrowing + // the member borrows the object's storage. Deeper levels of the result (a + // pointer/view member's own pointee) are the member's value, with no + // counterpart in the object's origin -- so the lists may differ in length + // and we flow just the top level, leaving the member's value untouched. + OriginList *Dst = getOriginsList(*BO); + OriginList *ObjSrc = + BO->getOpcode() == BO_PtrMemD + ? getOriginsList(*BO->getLHS()) + : getRValueOrigins(BO->getLHS(), getOriginsList(*BO->getLHS())); + if (Dst && ObjSrc) + CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( + Dst->getOuterOriginID(), ObjSrc->getOuterOriginID(), /*Kill=*/true)); + handleUse(BO->getLHS()); + return; + } if (BO->getOpcode() == BO_Comma) { killAndFlowOrigin(*BO, *BO->getRHS()); return; diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp index 6fc275b51a9d0..c1d96b84f023f 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -1962,6 +1962,66 @@ std::string_view refViewMemberReturnRefView1(RefMember a) { return a.view_ref; } std::string_view& refViewMemberReturnRefView2(RefMember a) { return a.view_ref; } } // namespace field_access +namespace pointer_to_member { +struct S { int x; void f() const; }; + +// `&(obj.*pm)` borrows the object, like `&obj.field`. +void via_dot_star() { + const int *p; + { + S s{5}; + int S::*pm = &S::x; + p = &(s.*pm); // expected-warning {{local variable 's' does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +void via_arrow_star() { + const int *p; + { + S s{5}; + int S::*pm = &S::x; + S *sp = &s; // expected-warning {{local variable 's' does not live long enough}} + p = &(sp->*pm); // expected-note {{local variable 'sp' aliases the storage of local variable 's'}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +// Negative: a long-lived object borrowed through `.*` stays silent. +void via_dot_star_ok() { + static S s{5}; + int S::*pm = &S::x; + const int *p = &(s.*pm); + (void)*p; // no-warning +} + +// A pointer/view member makes `obj.*pm` an origin one level deeper than the +// object; flowing only the outer (storage) level still ties the borrow to the +// object, so a dangle is caught. +struct V { std::string_view view; }; +void via_dot_star_view_member() { + std::string_view *p; + { + V v; + std::string_view V::*pm = &V::view; + p = &(v.*pm); // expected-warning {{local variable 'v' does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +// A pointer-to-member-function result is only callable (not storable), so it +// carries no borrow -- but calling one on a dangling object is still caught. +void via_member_function_ptr() { + void (S::*pmf)() const = &S::f; + S *sp; + { + S s; + sp = &s; // expected-warning {{local variable 's' does not live long enough}} + } // expected-note {{destroyed here}} + (sp->*pmf)(); // expected-note {{later used here}} +} +} // namespace pointer_to_member + namespace attr_on_template_params { struct MyObj { ~MyObj(); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
