Skip to content

Cell::swap assumes Cells never overlap #80778

Closed
@RalfJung

Description

@RalfJung

In #80682 (comment), it was uncovered that Cell::swap is making some rather strong assumptions: two Cells 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

Possible solutions

  • Accept that as_cell_of_array is unsound and document "non-overlap" as part of the Cell 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions