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
