Author: Gábor Horváth
Date: 2026-06-19T08:12:42Z
New Revision: b496d0623824060af20726f89bbf8fe662dd49e4

URL: 
https://github.com/llvm/llvm-project/commit/b496d0623824060af20726f89bbf8fe662dd49e4
DIFF: 
https://github.com/llvm/llvm-project/commit/b496d0623824060af20726f89bbf8fe662dd49e4.diff

LOG: [LifetimeSafety] Model bit_cast and atomic casts in the fact generator 
(#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

Co-authored-by: Gabor Horvath <[email protected]>

Added: 
    

Modified: 
    clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
    clang/lib/Analysis/LifetimeSafety/Origins.cpp
    clang/test/Sema/LifetimeSafety/safety-c.c
    clang/test/Sema/LifetimeSafety/safety.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 4b5a776b2bae7..3861117005752 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 3ff4823ca88a6..c837f246fa17b 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -106,6 +106,10 @@ bool OriginManager::hasOrigins(QualType QT, bool 
IntrinsicOnly) const {
   if (!IntrinsicOnly &&
       
LifetimeAnnotatedOriginTypes.contains(QT.getCanonicalType().getTypePtr()))
     return true;
+  // 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);
   const auto *RD = QT->getAsCXXRecordDecl();
   if (!RD)
     return false;
@@ -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 13b92a8d81db4..9ab2a57cb08a9 100644
--- a/clang/test/Sema/LifetimeSafety/safety-c.c
+++ b/clang/test/Sema/LifetimeSafety/safety-c.c
@@ -173,11 +173,24 @@ 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}}
 }
 
 // In C, a pointer compound assignment is a prvalue; its result still carries

diff  --git a/clang/test/Sema/LifetimeSafety/safety.cpp 
b/clang/test/Sema/LifetimeSafety/safety.cpp
index 7a2644e46a6e1..65bfe69e854ac 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -1435,6 +1435,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

Reply via email to