[clang] d65d7a1 - [LifetimeSafety] Count escape facts when classifying persistent origins (#204485)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 18 04:32:28 PDT 2026
Author: Gábor Horváth
Date: 2026-06-18T11:32:22Z
New Revision: d65d7a15997473c4831e8486b8aaf5242aecf99e
URL: https://github.com/llvm/llvm-project/commit/d65d7a15997473c4831e8486b8aaf5242aecf99e
DIFF: https://github.com/llvm/llvm-project/commit/d65d7a15997473c4831e8486b8aaf5242aecf99e.diff
LOG: [LifetimeSafety] Count escape facts when classifying persistent origins (#204485)
computePersistentOrigins marks an origin persistent (kept across CFG
blocks)
only if it appears in more than one block, but it omitted
OriginEscapesFact.
A global is not seeded at function entry, so a global assigned a stack
address
on a conditional or loop path had its origin appear only in the storing
block;
misclassified as block-local, its loan was dropped at the join before
the
escape check, a silently missed dangling-global
(stack-use-after-return).
Count the escaped origin as a cross-block appearance.
Assisted-by: Claude Opus 4.8
Co-authored-by: Gabor Horvath <gaborh at apple.com>
Added:
Modified:
clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
clang/test/Sema/LifetimeSafety/dangling-field.cpp
clang/test/Sema/LifetimeSafety/dangling-global.cpp
Removed:
################################################################################
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index f3bb85f08e965..a67b1b3c0f826 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -66,8 +66,12 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr,
case Fact::Kind::KillOrigin:
CheckOrigin(F->getAs<KillOriginFact>()->getKilledOrigin());
break;
- case Fact::Kind::MovedOrigin:
case Fact::Kind::OriginEscapes:
+ // An escaping origin is read at the exit block but defined earlier, so
+ // it spans blocks and must participate in joins.
+ CheckOrigin(F->getAs<OriginEscapesFact>()->getEscapedOriginID());
+ break;
+ case Fact::Kind::MovedOrigin:
case Fact::Kind::Expire:
case Fact::Kind::TestPoint:
case Fact::Kind::InvalidateOrigin:
diff --git a/clang/test/Sema/LifetimeSafety/dangling-field.cpp b/clang/test/Sema/LifetimeSafety/dangling-field.cpp
index ab7c49d9c7bfd..b1eb31c4ee486 100644
--- a/clang/test/Sema/LifetimeSafety/dangling-field.cpp
+++ b/clang/test/Sema/LifetimeSafety/dangling-field.cpp
@@ -19,6 +19,25 @@ struct CtorSet {
CtorSet(std::string s) { view = s; } // expected-warning {{stack memory associated with parameter 's' escapes to the field 'view' which will dangle}}
};
+// A field escape on some-but-not-all paths (or in a loop) must still be caught:
+// the field's origin only spans blocks via the exit escape, so it must survive
+// the join.
+struct CtorSetConditional {
+ std::string_view view; // expected-note {{this field dangles}}
+ CtorSetConditional(std::string s, bool c) {
+ if (c)
+ view = s; // expected-warning {{stack memory associated with parameter 's' escapes to the field 'view' which will dangle}}
+ }
+};
+
+struct CtorSetInLoop {
+ std::string_view view; // expected-note {{this field dangles}}
+ CtorSetInLoop(std::string s, int n) {
+ for (int i = 0; i < n; ++i)
+ view = s; // expected-warning {{stack memory associated with parameter 's' escapes to the field 'view' which will dangle}}
+ }
+};
+
struct CtorInitLifetimeBound {
std::string_view view; // expected-note {{this field dangles}}
CtorInitLifetimeBound(std::string s) : view(construct_view(s)) {} // expected-warning {{stack memory associated with parameter 's' escapes to the field 'view' which will dangle}}
diff --git a/clang/test/Sema/LifetimeSafety/dangling-global.cpp b/clang/test/Sema/LifetimeSafety/dangling-global.cpp
index f419ff4416023..8a96cbced43b4 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 4 {{this global dangles}}
int *global_backup; // expected-note {{this global dangles}}
struct ObjWithStaticField {
@@ -48,3 +48,25 @@ 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}}
}
+
+// A store on some-but-not-all paths must still be caught: the global's origin
+// only spans blocks via the function-exit escape, so it must survive the join.
+void conditional_escape(int c) {
+ int local = 7;
+ if (c)
+ global = &local; // expected-warning {{stack memory associated with local variable 'local' escapes to the global variable 'global' which will dangle}}
+}
+
+void loop_escape(int n) {
+ int local = 0;
+ for (int i = 0; i < n; ++i)
+ global = &local; // expected-warning {{stack memory associated with local variable 'local' escapes to the global variable 'global' which will dangle}}
+}
+
+// Negative: a conditional store that never leaks a stack address is silent.
+void conditional_no_escape(int c) {
+ int local = 7;
+ if (c)
+ global = nullptr; // no-warning
+ (void)local;
+}
More information about the cfe-commits
mailing list