Skip to content

What assumptions can safe-interface functions make about &Cells of different types aliasing? #586

@ais523

Description

@ais523

Background: Suppose we have two functions, each of which potentially uses unsafe code internally but presents a safe interface. f returns a value of some type T, and g takes a T as its argument. We want to prove that g(f()) is sound (i.e. no undefined behaviour), which means that anything that g assumes is true about its argument for its safety proof must be ensured by f, and that in turn depends on what g's safety assumptions are. As such, some explicit choice needs to be made for "the safety assumptions that a function with a safe interface is allowed to assume" – making these less stringent makes g harder to write, making them more stringent makes f harder to write.

My question is about what explicit choice should be made for these "safe-interface safety assumptions" with respect to aliasing &Cells. It's clear that some combinations must be disallowed, because they could lead to UB in safe code. For example, this (safe interface and internally safe) function has undefined behaviour if its arguments alias:

fn f1(x: &Cell<&'static usize>, y: &Cell<*const usize>) -> usize {
    y.set(core::ptr::null());
    *x.get()
}

A similar problem can happen with a single argument, if it's a cell that aliases its own contents (this is UB if the &'static Cell<usize> inside x is an alias for x itself, which can happen if the memory location *x holds a pointer to itself):

fn f2(x: &Cell<&'static Cell<usize>>) -> usize {
    x.get().set(0);
    x.get().get()
}

On the other hand, there are some similar cases which can't (in current stable Rust), as far as I know, lead to undefined behaviour in code that uses no unsafe internally. For example, a &Cell<Singleton<&'static Cell<usize>>> (where Singleton<T> is a non-Copy transparent public-field wrapper around T but can only be constructed once per program execution) probably can't be directly exploited in safe Rust because the only way to observe the value inside the Singleton would be to move the Singleton out of the cell, but it can't be moved out because there's no way to construct a replacement Singleton to swap/replace it with.

It would be possible to say that the explicit choice for the safe-interface safety assumption about aliasing Cells would be "anything that can't be used to cause undefined behaviour without unsafe is allowed". But this would reject some useful code; in particular I'm thinking of rust-lang/rust#145329 which wants to be able to make a safety assumption that an &Cell<Rc<T>> doesn't alias its own reference count (which is a Cell<usize> accessible via a pointer contained in the Rc). But in stable Rust, without using unsafe internally, such a Cell<Rc<T>> doesn't seem to be able to lead to undefined behaviour in code that does not have a T nor Rc<T> and is unable to construct one (because without access to an Rc<T>, you don't have anything to move into the Cell, and thus can't use any of the methods of &Cell except swap and as_ptr which don't help). So it would be nice if the safe-interface safety assumption were a little stronger, making it possible to write a sound Cell::get_cloned().

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions