[clang] [LifetimeSafety] Propagate loans through pointer inc/dec and compound assignment (PR #204477)
Gábor Horváth via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 18 07:44:12 PDT 2026
https://github.com/Xazax-hun updated https://github.com/llvm/llvm-project/pull/204477
>From 5640d7a9f2648373937f847d90068203409e224e Mon Sep 17 00:00:00 2001
From: Gabor Horvath <gaborh at apple.com>
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 | 26 +++++++++-
.../Sema/LifetimeSafety/dangling-global.cpp | 49 ++++++++++++++++++-
clang/test/Sema/LifetimeSafety/safety-c.c | 22 +++++++++
3 files changed, 95 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index d56703a4b29c4..7c620483242f0 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: {
+ // Inc/dec keeps a pointer in the same allocation, so the result carries the
+ // operand's loans. Peel the operand's storage origin when the *result* is a
+ // prvalue (post-inc/dec, or any form in C) -- the inverse of
+ // getRValueOrigins, which peels when its own argument is a glvalue.
+ 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,17 @@ void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
killAndFlowOrigin(*BO, *BO->getRHS());
return;
}
- if (BO->isCompoundAssignmentOp())
+ if (BO->isCompoundAssignmentOp()) {
+ // A pointer compound additive assignment (`p += n`) carries the LHS's loans
+ // like inc/dec above; in C the result is a prvalue, so peel its outer
+ // (storage) origin.
+ if (BO->getType()->isPointerType()) {
+ OriginList *LHSList = getOriginsList(*BO->getLHS());
+ flow(getOriginsList(*BO), IsCMode ? LHSList->peelOuterOrigin() : 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 8a96cbced43b4..8d464b0dbe554 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 4 {{this global dangles}}
+int *global; // expected-note 10 {{this global dangles}}
int *global_backup; // expected-note {{this global dangles}}
struct ObjWithStaticField {
@@ -70,3 +70,50 @@ void conditional_no_escape(int c) {
global = nullptr; // no-warning
(void)local;
}
+
+// 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}}
+}
More information about the cfe-commits
mailing list