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

Reply via email to