https://github.com/Xazax-hun updated 
https://github.com/llvm/llvm-project/pull/204477

From 98ad02b3e14b499d522a2f2b0e5db59bae04921f Mon Sep 17 00:00:00 2001
From: Gabor Horvath <[email protected]>
Date: Wed, 17 Jun 2026 23:19:27 +0100
Subject: [PATCH] [LifetimeSafety] Propagate loans through pointer inc/dec and
 compound assignment

VisitUnaryOperator modeled only address-of/deref, and VisitBinaryOperator
early-returned for compound assignments, so a borrow used via the result of
`++p`/`p++`/`p += n` etc. was silently dropped (e.g. `global = ++p;` missed
-Wlifetime-safety-dangling-global). These keep the pointer in the same
allocation, so flow the operand's loans into the result, peeling the storage
origin when the result is a prvalue (post-inc/dec, or any form in C).

Assisted-by: Claude Opus 4.8
---
 .../LifetimeSafety/FactsGenerator.cpp         | 27 +++++++++-
 .../Sema/LifetimeSafety/dangling-global.cpp   | 49 ++++++++++++++++++-
 clang/test/Sema/LifetimeSafety/safety-c.c     | 22 +++++++++
 3 files changed, 96 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index d56703a4b29c4..d769fd8e5d56a 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -372,6 +372,21 @@ void FactsGenerator::VisitUnaryOperator(const 
UnaryOperator *UO) {
     killAndFlowOrigin(*UO, *SubExpr);
     return;
   }
+  case UO_PreInc:
+  case UO_PostInc:
+  case UO_PreDec:
+  case UO_PostDec: {
+    // Incrementing/decrementing a pointer keeps it in the same allocation, so
+    // the result carries the operand's loans. The operand is always an lvalue;
+    // peel its storage origin when the result is a prvalue (post-inc/dec, or
+    // any form in C).
+    if (!UO->getType()->isPointerType())
+      return;
+    OriginList *SubList = getOriginsList(*UO->getSubExpr());
+    flow(getOriginsList(*UO),
+         UO->isGLValue() ? SubList : SubList->peelOuterOrigin(), 
/*Kill=*/true);
+    return;
+  }
   default:
     return;
   }
@@ -472,8 +487,18 @@ void FactsGenerator::VisitBinaryOperator(const 
BinaryOperator *BO) {
     killAndFlowOrigin(*BO, *BO->getRHS());
     return;
   }
-  if (BO->isCompoundAssignmentOp())
+  if (BO->isCompoundAssignmentOp()) {
+    // A pointer compound additive assignment (`p += n`) keeps the pointer in
+    // the same allocation, so its result carries the LHS pointer's loans. In C
+    // the result is a prvalue, so peel the LHS's storage origin.
+    if (BO->getType()->isPointerType()) {
+      OriginList *LHSList = getOriginsList(*BO->getLHS());
+      flow(getOriginsList(*BO),
+           IsCMode ? getRValueOrigins(BO->getLHS(), LHSList) : LHSList,
+           /*Kill=*/true);
+    }
     return;
+  }
   if (BO->getType()->isPointerType() && BO->isAdditiveOp())
     handlePointerArithmetic(BO);
   handleUse(BO->getRHS());
diff --git a/clang/test/Sema/LifetimeSafety/dangling-global.cpp 
b/clang/test/Sema/LifetimeSafety/dangling-global.cpp
index f419ff4416023..d2636f779ef74 100644
--- a/clang/test/Sema/LifetimeSafety/dangling-global.cpp
+++ b/clang/test/Sema/LifetimeSafety/dangling-global.cpp
@@ -1,6 +1,6 @@
 // RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify %s
 
-int *global; // expected-note 2 {{this global dangles}}
+int *global; // expected-note 8 {{this global dangles}}
 int *global_backup; // expected-note {{this global dangles}}
 
 struct ObjWithStaticField {
@@ -48,3 +48,50 @@ void dangling_static_field() {
   int local;
   ObjWithStaticField::static_field = &local; // expected-warning {{stack 
memory associated with local variable 'local' escapes to the static variable 
'static_field' which will dangle}}
 }
+
+// Pointer compound assignment and increment/decrement keep the pointer in the
+// same allocation, so the result carries the borrow.
+void via_compound_add() {
+  int local[10];
+  int *p = local; // expected-warning {{stack memory associated with local 
variable 'local' escapes to the global variable 'global' which will dangle}}
+  global = (p += 1);
+}
+
+void via_compound_sub() {
+  int local[10];
+  int *p = local + 5; // expected-warning {{stack memory associated with local 
variable 'local' escapes to the global variable 'global' which will dangle}}
+  global = (p -= 1);
+}
+
+void via_preinc() {
+  int local[10];
+  int *p = local; // expected-warning {{stack memory associated with local 
variable 'local' escapes to the global variable 'global' which will dangle}}
+  global = ++p;
+}
+
+void via_postinc() {
+  int local[10];
+  int *p = local; // expected-warning {{stack memory associated with local 
variable 'local' escapes to the global variable 'global' which will dangle}}
+  global = p++;
+}
+
+void via_predec() {
+  int local[10];
+  int *p = local + 5; // expected-warning {{stack memory associated with local 
variable 'local' escapes to the global variable 'global' which will dangle}}
+  global = --p;
+}
+
+void via_postdec() {
+  int local[10];
+  int *p = local + 5; // expected-warning {{stack memory associated with local 
variable 'local' escapes to the global variable 'global' which will dangle}}
+  global = p--;
+}
+
+// Negative: arithmetic on a pointer into long-lived storage stays silent.
+void ok_global_storage() {
+  static int s[10];
+  int *p = s;
+  p += 1;
+  ++p;
+  global = (p -= 1); // no-warning
+}
diff --git a/clang/test/Sema/LifetimeSafety/safety-c.c 
b/clang/test/Sema/LifetimeSafety/safety-c.c
index 95c8cf7bb00c7..13b92a8d81db4 100644
--- a/clang/test/Sema/LifetimeSafety/safety-c.c
+++ b/clang/test/Sema/LifetimeSafety/safety-c.c
@@ -179,3 +179,25 @@ int *atomic_pointer_declref(void) {
   _Atomic(int *) p = &value;
   return p;
 }
+
+// In C, a pointer compound assignment is a prvalue; its result still carries
+// the LHS pointer's loans.
+void compound_assign_prvalue(void) {
+  int *p;
+  {
+    int local[10];
+    int *q = local; // expected-warning {{local variable 'local' does not live 
long enough}}
+    p = (q += 1);
+  }               // expected-note {{destroyed here}}
+  (void)*p;       // expected-note {{later used here}}
+}
+
+void preincrement_prvalue(void) {
+  int *p;
+  {
+    int local[10];
+    int *q = local; // expected-warning {{local variable 'local' does not live 
long enough}}
+    p = ++q;
+  }               // expected-note {{destroyed here}}
+  (void)*p;       // expected-note {{later used here}}
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to