https://github.com/Xazax-hun created https://github.com/llvm/llvm-project/pull/204591
VisitCastExpr dropped several borrow-carrying cast kinds into its default case. Propagate the borrow through `__builtin_bit_cast`/`std::bit_cast` of a pointer and through wrapping/unwrapping `_Atomic(T*)`, so a stack address laundered through either is caught (matching reinterpret_cast). hasOrigins and buildListForType now see through AtomicType, which is transparent for lifetimes. Assisted-by: Claude Opus 4.8 From 5dbb22e15518fa28e5d9a370d7b6348b5ed44ce2 Mon Sep 17 00:00:00 2001 From: Gabor Horvath <[email protected]> Date: Thu, 18 Jun 2026 14:14:53 +0100 Subject: [PATCH] [LifetimeSafety] Model bit_cast and atomic casts in the fact generator VisitCastExpr dropped several borrow-carrying cast kinds into its default case. Propagate the borrow through `__builtin_bit_cast`/`std::bit_cast` of a pointer and through wrapping/unwrapping `_Atomic(T*)`, so a stack address laundered through either is caught (matching reinterpret_cast). hasOrigins and buildListForType now see through AtomicType, which is transparent for lifetimes. Assisted-by: Claude Opus 4.8 --- .../LifetimeSafety/FactsGenerator.cpp | 14 ++++++++++ clang/lib/Analysis/LifetimeSafety/Origins.cpp | 7 +++++ clang/test/Sema/LifetimeSafety/safety-c.c | 17 +++++++++-- clang/test/Sema/LifetimeSafety/safety.cpp | 28 +++++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index d56703a4b29c4..76afd921d8d6b 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -342,6 +342,20 @@ void FactsGenerator::VisitCastExpr(const CastExpr *CE) { if (Src && Dest && Dest->getLength() == Src->getLength()) flow(Dest, Src, /*Kill=*/true); return; + case CK_LValueToRValueBitCast: + case CK_NonAtomicToAtomic: + case CK_AtomicToNonAtomic: { + // `__builtin_bit_cast`/`std::bit_cast` of a pointer, and + // wrapping/unwrapping `_Atomic(T*)`, preserve the pointer value, so + // propagate the borrow. The operand may be a glvalue, so strip its outer + // lvalue level first. A bit-cast that materializes a pointer from a + // non-pointer representation has no matching source origin and is + // untracked. + OriginList *RVSrc = getRValueOrigins(SubExpr, Src); + if (RVSrc && Dest->getLength() == RVSrc->getLength()) + flow(Dest, RVSrc, /*Kill=*/true); + return; + } default: return; } diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp index 55d3b36e3163a..4b7e512d988e8 100644 --- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp @@ -101,6 +101,10 @@ class LifetimeAnnotatedOriginTypeCollector } // namespace bool OriginManager::hasOrigins(QualType QT, bool IntrinsicOnly) const { + // An `_Atomic(T)` wraps T transparently for lifetime purposes (the atomic + // holds the same value); see through it. + if (const auto *AT = QT->getAs<AtomicType>()) + return hasOrigins(AT->getValueType(), IntrinsicOnly); if (QT->isPointerOrReferenceType() || isGslPointerType(QT)) return true; if (!IntrinsicOnly && @@ -194,6 +198,9 @@ OriginList *OriginManager::createSingleOriginList(OriginID OID) { template <typename T> OriginList *OriginManager::buildListForType(QualType QT, const T *Node) { assert(hasOrigins(QT) && "buildListForType called for non-pointer type"); + // `_Atomic(T)` is transparent for lifetime purposes: build the node for T. + if (const auto *AT = QT->getAs<AtomicType>()) + return buildListForType(AT->getValueType(), Node); OriginList *Head = createNode(Node, QT); if (QT->isPointerOrReferenceType()) { diff --git a/clang/test/Sema/LifetimeSafety/safety-c.c b/clang/test/Sema/LifetimeSafety/safety-c.c index 95c8cf7bb00c7..4170c9d56c95c 100644 --- a/clang/test/Sema/LifetimeSafety/safety-c.c +++ b/clang/test/Sema/LifetimeSafety/safety-c.c @@ -173,9 +173,22 @@ void *void_pointer_dereference(void) { return &*bytes; } -// FIXME: Atomics are not modeled yet. +// `_Atomic(T)` is transparent for lifetime purposes; a stack address laundered +// through an atomic is caught. int *atomic_pointer_declref(void) { int value; + _Atomic(int *) p = &value; // expected-warning {{stack memory associated with local variable 'value' is returned}} + return p; // expected-note {{returned here}} +} + +int *atomic_pointer_static(void) { + static int value; _Atomic(int *) p = &value; - return p; + return p; // no-warning +} + +int **atomic_pointer_multilevel(void) { + int *inner; + _Atomic(int **) p = &inner; // expected-warning {{stack memory associated with local variable 'inner' is returned}} + return p; // expected-note {{returned here}} } diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp index 6fc275b51a9d0..2fd73e9c5d739 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -1353,6 +1353,34 @@ void use_trivial_temporary_after_destruction() { use(a); // expected-note {{later used here}} } +namespace cast_modeling { +// A pointer bit-cast (`__builtin_bit_cast`/`std::bit_cast`) preserves the +// value, so a borrow flowed through it is tracked (matching reinterpret_cast). +int *bit_cast_stack() { + int x = 0; + return __builtin_bit_cast(int *, &x); // expected-warning {{stack memory associated with local variable 'x' is returned}} expected-note {{returned here}} +} + +int *bit_cast_static() { + static int s = 0; + return __builtin_bit_cast(int *, &s); // no-warning +} + +void bit_cast_use_after_scope() { + int *p; + { + int local = 0; + p = __builtin_bit_cast(int *, &local); // expected-warning {{local variable 'local' does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +int **bit_cast_multilevel() { + int *p = nullptr; + return __builtin_bit_cast(int **, &p); // expected-warning {{stack memory associated with local variable 'p' is returned}} expected-note {{returned here}} +} +} // namespace cast_modeling + namespace FullExprCleanupLoc { void var_initializer() { View v = non_trivially_destructed_temporary() // expected-warning {{temporary object does not live long enough}} \ _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
