[clang] [LifetimeSafety] Model bit_cast and atomic casts in the fact generator (PR #204591)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 18 06:27:54 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-analysis
Author: Gábor Horváth (Xazax-hun)
<details>
<summary>Changes</summary>
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
---
Full diff: https://github.com/llvm/llvm-project/pull/204591.diff
4 Files Affected:
- (modified) clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp (+14)
- (modified) clang/lib/Analysis/LifetimeSafety/Origins.cpp (+7)
- (modified) clang/test/Sema/LifetimeSafety/safety-c.c (+15-2)
- (modified) clang/test/Sema/LifetimeSafety/safety.cpp (+28)
``````````diff
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}} \
``````````
</details>
https://github.com/llvm/llvm-project/pull/204591
More information about the cfe-commits
mailing list