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 <[email protected]>

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;
+}


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

Reply via email to