|
| 1 | +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. |
| 2 | +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. |
| 3 | + |
| 4 | +## Known cases of deliberate UB |
| 5 | + |
| 6 | +### Cases related to concurrency |
| 7 | + |
| 8 | +* crossbeam's `AtomicCell` "fallback path" uses a SeqLock, which [is well-known to not be compatible with the C++ memory model](http://www.hpl.hp.com/techreports/2012/HPL-2012-68.pdf). |
| 9 | + Specifically the problem is [this non-atomic volatile read](https://github.com/crossbeam-rs/crossbeam/blob/5d07fe43540d7f21517a51813acd9332744e90cb/crossbeam-utils/src/atomic/atomic_cell.rs#L980) which can cause data races and hence UB. |
| 10 | + This would be fine if we either (a) adopted LLVM's handling of memory races (then the problematic read would merely return `undef` instead of UB due to a data race), or (b) added [bytewise atomic memcpy](https://github.com/rust-lang/rfcs/pull/3301) and used that instead of the non-atomic volatile load. |
| 11 | + 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 uses `MaybeUninit`. |
| 12 | +* crossbeam's `AtomicCell` "fast path" uses the standard library `Atomic*` types to do atomic reads and writes of [*any type* that has the right size](https://github.com/crossbeam-rs/crossbeam/blob/master/crossbeam-utils/src/atomic/atomic_cell.rs#L928-L932). |
| 13 | + However, doing an `AtomicU32` read (returning a `u32`) on a `(u16, u8)` is unsound because the padding byte can be uninitialized. |
| 14 | + (Also, pointer provenance is lost, so `AtomicCell<*const T>` does not behave the way people expect.) |
| 15 | + To fix this we need to be able to perform atomic loads at type `MaybeUninit<u32>`. |
| 16 | + The LLVM IR we generate here contains `noundef` so this UB is not just a specification artifact.<br> |
| 17 | + Furthermore, `compare_exchange` will compare padding bytes, which leads to UB. |
| 18 | + It is not clear how to best specify a useful `compare_exchange` that can work on padding bytes, |
| 19 | + see the [discussion here](https://github.com/rust-lang/unsafe-code-guidelines/issues/449). |
| 20 | + |
| 21 | +### Cases related to aliasing |
| 22 | + |
| 23 | +* `yoke` and similar crates relying in "stable deref" properties cause various forms of aliasing trouble (such as [having `Box` that alias with things](https://github.com/unicode-org/icu4x/issues/2095), or [having references in function arguments that get deallocated while the function runs](https://github.com/unicode-org/icu4x/issues/3696)). |
| 24 | + This also violates the LLVM assumptions that come with `noalias` and `dereferenceable`. |
| 25 | + This could be fixed by [`MaybeDangling`](https://github.com/rust-lang/rfcs/pull/3336). |
| 26 | +* 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. |
| 27 | + This is currently hackfixed by making `Unpin` meaningful for UB; a proper solution would be to add something like [`UnsafeAliased`](https://github.com/rust-lang/rfcs/pull/3467). |
| 28 | +* Stacked Borrows forbids a bunch of things that might be considered too restrictive (and that go well beyond LLVM `noalias`): |
| 29 | + strict subobject provenance [rules out the `&Header` pattern](https://github.com/rust-lang/unsafe-code-guidelines/issues/256) and also affects [raw pointers derived from references](https://github.com/rust-lang/unsafe-code-guidelines/issues/134); |
| 30 | + eager assertion of uniquess makes [even read-only functions such as `as_mut_ptr` dangerous when they take `&mut`](https://github.com/rust-lang/unsafe-code-guidelines/issues/133); |
| 31 | + `&UnsafeCell` surprisingly [requires read-write memory even when it is never written](https://github.com/rust-lang/unsafe-code-guidelines/issues/303). |
| 32 | + There is a bunch of code out there that violates these rules one way or another. |
| 33 | + All of these are resolved by [Tree Borrows](https://perso.crans.org/vanille/treebor/), though [some subtleties around `as_mut_ptr` do remain](https://github.com/rust-lang/unsafe-code-guidelines/issues/450). |
| 34 | + |
| 35 | +## Former cases of deliberate UB that have at least a work-in-progress solution to them |
| 36 | + |
| 37 | +* Various `offset_of` implementations caused UB by using `mem::uninitialized()`, or they used `&(*base).field` or `addr_of!((*base).field)` to project a dummy pointer to the field which is UB due to out-of-bounds pointer arithmetic. |
| 38 | + The `memoffset` crate has a sound implementation that however causes stack allocations which the compiler must optimize away. |
| 39 | + This will be fixed properly by the native [`offset_of!` macro](https://github.com/rust-lang/rfcs/pull/3308), which is [currently in nightly](https://github.com/rust-lang/rust/issues/106655). |
| 40 | +* It used to be common to unwind out of `extern "C"` functions which is UB, see [this discussions](https://internals.rust-lang.org/t/unwinding-through-ffi-after-rust-1-33/9521). |
| 41 | + This is fixed by `extern "C-unwind"`, which is stable since Rust 1.71. |
0 commit comments