Description
In #80682 (comment), it was uncovered that Cell::swap
is making some rather strong assumptions: two Cell
s with different address but the same type must not overlap. Not only is this a scarily non-local safety invariant, it is also fundamentally incomaptible with some APIs that ought to be correct, as demonstrated by this snippet (thanks to @xfix and @SkiFire13 for help with working out the example):
use std::cell::Cell;
use std::mem::transmute;
use std::convert::TryInto;
fn as_cell_of_array<T, const N: usize>(c: &[Cell<T>; N]) -> &Cell<[T; N]> {
unsafe { transmute(c) }
}
fn main() {
let x = [Cell::new(vec![1]), Cell::new(vec![2]),Cell::new(vec![3])];
let x1: &Cell<[_; 2]> = as_cell_of_array(x[0..2].try_into().unwrap());
let x2: &Cell<[_; 2]> = as_cell_of_array(x[1..3].try_into().unwrap());
x1.swap(x2);
}
Run it in Miri to see the issue: ptr::swap
will duplicate parts of the memory range when there is overlap, which leads to double-drop (other parts of the memory range are just lost, leading to memory leaks, but that is not the main issue here).
This is not itself a soundness issue as it requires unsafe code to trigger UB. But this likely reflects an unintended consequence of Cell::swap
.
Cell::swap
history
- This got stabilized in 33a5665 as part of Library stabilizations for 1.17 #40538. That commit references Tracking issue for RFC 1651: Extend Cell to non-Copy types #39264 and an RFC, but
swap
is not mentioned in either of them. swap
was added in Addswap
method forCell
#39716.
Possible solutions
- Accept that
as_cell_of_array
is unsound and document "non-overlap" as part of theCell
safety invariant. This seems very fragile. - Make
Cell::swap
not misbehave on overlap, either by panicking or by only swapping the non-overlapping parts.
Cc @rust-lang/lang