[clang] [LifetimeSafety] Model pointer-to-data-member access in the fact generator (PR #204612)
Gábor Horváth via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 18 09:31:25 PDT 2026
https://github.com/Xazax-hun updated https://github.com/llvm/llvm-project/pull/204612
>From 6df3bb7af806cb30278c3a01f99da72b938a62e8 Mon Sep 17 00:00:00 2001
From: Gabor Horvath <gaborh at apple.com>
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 | 14 ++++++
clang/test/Sema/LifetimeSafety/safety.cpp | 46 +++++++++++++++++++
2 files changed, 60 insertions(+)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index d56703a4b29c4..bfa6e6e3e8347 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -468,6 +468,20 @@ 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.
+ OriginList *Dst = getOriginsList(*BO);
+ OriginList *ObjSrc =
+ BO->getOpcode() == BO_PtrMemD
+ ? getOriginsList(*BO->getLHS())
+ : getRValueOrigins(BO->getLHS(), getOriginsList(*BO->getLHS()));
+ if (Dst && ObjSrc && Dst->getLength() == ObjSrc->getLength())
+ flow(Dst, ObjSrc, /*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..69c1605ce9708 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -1962,6 +1962,52 @@ 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-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();
More information about the cfe-commits
mailing list