When a crate explicitly acknowledges that what it does is UB, but prefers keeping that code over UB-free alternatives (or there are no UB-free alternatives), that is always a concerning sign. We should evaluate whether there truly is some use-case here that is not currently achievable in well-defined Rust, and work with crate authors on providing a UB-free solution.
- crossbeam's
AtomicCell
"fallback path" uses a SeqLock, which is well-known to not be compatible with the C++ memory model. Specifically the problem is this non-atomic volatile read which can cause data races and hence UB. This would be fine if we either (a) adopted LLVM's handling of memory races (then the problematic read would merely returnundef
instead of UB due to a data race), or (b) added bytewise atomic memcpy and used that instead of the non-atomic volatile load. This is currently not UB in the LLVM IR we generate, due to LLVM's different handling of read-write races and because the code carefully usesMaybeUninit
. - crossbeam's
AtomicCell
"fast path" uses the standard libraryAtomic*
types to do atomic reads and writes of any type that has the right size. However, doing anAtomicU32
read (returning au32
) on a(u16, u8)
is unsound because the padding byte can be uninitialized. (Also, pointer provenance is lost, soAtomicCell<*const T>
does not behave the way people expect.) To fix this we need to be able to perform atomic loads at typeMaybeUninit<u32>
. The LLVM IR we generate here containsnoundef
so this UB is not just a specification artifact.
Furthermore,compare_exchange
will compare padding bytes, which leads to UB. It is not clear how to best specify a usefulcompare_exchange
that can work on padding bytes, see the discussion here.
The alternative is to not use the "fast path" for problematic types (and fall back to the SeqLock), but that requires some way to query atconst
-time whether the type contains padding (or provenance). (Or of course one can use inline assembly, but it would be better if that was not required.) - tokio causes race conditions between atomic and non-atomic loads which are not permitted; see this issue for details.
yoke
and similar crates relying in "stable deref" properties cause various forms of aliasing trouble (such as havingBox
that alias with things, or having references in function arguments that get deallocated while the function runs). This also violates the LLVM assumptions that come withnoalias
anddereferenceable
. This could be fixed byMaybeDangling
.- The entire
async fn
ecosystem and every hand-implemented self-referential generator or future is unsound since the self-reference aliases the&mut
reference to the full generator/future. This is currently hackfixed by makingUnpin
meaningful for UB; a proper solution would be to add something likeUnsafeAliased
. - Stacked Borrows forbids a bunch of things that might be considered too restrictive (and that go well beyond LLVM
noalias
): strict subobject provenance rules out the&Header
pattern and also affects raw pointers derived from references; eager assertion of uniquess makes even read-only functions such asas_mut_ptr
dangerous when they take&mut
;&UnsafeCell
surprisingly requires read-write memory even when it is never written. There is a bunch of code out there that violates these rules one way or another. All of these are resolved by Tree Borrows, though some subtleties aroundas_mut_ptr
do remain.
- Various
offset_of
implementations caused UB by usingmem::uninitialized()
, or they used&(*base).field
oraddr_of!((*base).field)
to project a dummy pointer to the field which is UB due to out-of-bounds pointer arithmetic. Thememoffset
crate has a sound implementation that however causes stack allocations which the compiler must optimize away. This will be fixed properly by the nativeoffset_of!
macro, which is currently in nightly. - It used to be common to unwind out of
extern "C"
functions which is UB, see this discussions. This is fixed byextern "C-unwind"
, which is stable since Rust 1.71.