Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can a &UnsafeCell<T> and a &mut UnsafeCell<T> alias? #284

Closed
SkiFire13 opened this issue May 21, 2021 · 5 comments
Closed

Can a &UnsafeCell<T> and a &mut UnsafeCell<T> alias? #284

SkiFire13 opened this issue May 21, 2021 · 5 comments

Comments

@SkiFire13
Copy link

SkiFire13 commented May 21, 2021

See https://users.rust-lang.org/t/can-you-break-the-lift/58858 for the original discussion (it actually starts from the 12th post)

The original question takes a &UnsafeCell<T> and, supposing that nobody is borrowing the inner value, gets a mutable reference (&mut T) to the inner value, then trasmute it to a &mut UnsafeCell<T>, creating a &mut UnsafeCell<T> that alias with another &UnsafeCell<T>. Is this sound? Does this also mean you can transmute a &UnsafeCell<T> to a &mut UnsafeCell<T> (assuming no other reference to the inner value exist)?

Related: jasoncarr0 also showed you can safely do something like this with GhostCell.

Also related: rust-lang/rust#63787 since that's also caused by a &UnsafeCell<T> aliasing with a reference to itself, even though in that case it's a reference to the inner value. While that may be solved by storing a raw pointer inside Ref, it's not an actual possibility here.

I feel like that either this is unsound or the rust aliasing model is fundamentally incompatible with LLVM's noalias.

@phlopsi
Copy link

phlopsi commented May 22, 2021

What you describe is not semantically different from reborrowing, which can be done in purely safe code:

fn main() {
    let mut a = String::from("Hello World!");
    let b = &mut a;
    println!("{:p}", b);
    let c = &mut *b;
    // ↑ Here, both `b` and `c` point to the same address
    println!("{:p}", c);
    println!("{:p}", b);
    // ↑ Here, `c` is invalidated, because `b` reclaimed the exclusivity to `a`
    // and trying to use it, would result in a compile-time error:
    // println!("{:p}", c);
}

(Playground)

The only difference is, that you have to manage the lifetimes yourself, i.e. Rust won't be complaining, if you do uncomment the last println! when using UnsafeCell.

EDIT:
As for

Does this also mean you can transmute a &UnsafeCell<T> to a &mut UnsafeCell<T>

Assuming

let my_var: *mut MyType = …;
let my_mut_ref: &mut MyType = unsafe { &mut *my_var };

and

let my_var: *mut MyType = …;
let my_mut_ref: &mut MyType =
unsafe { transmute(my_var) };

have the same semantics, then I'd assume doing

let my_var: *mut MyType = …;
let my_mut_ref: &mut UnsafeCell<MyType> =
unsafe { transmute(my_var) };

is valid, as well, because UnsafeCell<T> is #[repr(transparent)]. This implies, that

let my_original_var: MyType = …;
let my_cell_var: UnsafeCell<MyType> = UnsafeCell::new(my_original_var);
let my_var: *mut MyType = my_cell_var.get();
let my_mut_ref: &mut UnsafeCell<MyType> =
unsafe { transmute(my_var) };

is valid, too.

However, transmuting &UnsafeCell to &mut UnsafeCell is UB according to the Rustonomicon, for the following "reasons":

Transmuting an & to &mut is UB.

  • Transmuting an & to &mut is always UB.
  • No you can't do it.
  • No you're not special.

This implies to me, that you must go through UnsafeCell::<T>::get, first, before obtaining an exclusive reference to its interior, probably, because the method is what causes the Rust compiler to tag the resulting pointer correctly for LLVM while transmutation does not.

@matthieu-m
Copy link

It's precisely the lifetime's relationship I find slightly surprising.

Consider this example:

use std::cell::UnsafeCell;

fn transform<T>(from: &UnsafeCell<T>) -> &mut UnsafeCell<T> {
    unsafe { &mut *(from.get() as *mut UnsafeCell<T>) }
}

fn main() {
    let mut cell = UnsafeCell::new("Hello".to_string());

    let derived = transform(&cell);

    let ptr = cell.get();

    println!("{:?} -> {:?}", ptr, derived.get_mut());
}

I am surprised to see that cell.get() is allowed between the point where derived is materialized and the point it's used.

A slight version of it is rejected, due to considering that cell is still borrowed when calling cell.get_mut():

use std::cell::UnsafeCell;

fn transform<T>(from: &UnsafeCell<T>) -> &mut UnsafeCell<T> {
    unsafe { &mut *(from.get() as *mut UnsafeCell<T>) }
}

fn main() {
    let mut cell = UnsafeCell::new("Hello".to_string());

    let derived = transform(&cell);

    println!("{:?} -> {:?}", cell.get_mut(), derived.get_mut());
}

@RalfJung
Copy link
Member

RalfJung commented May 22, 2021

I think there's a lot of red herrings here. The underlying thing to keep in mind is

&mut T and &mut UnsafeCell<T> are, for all intents and purposes, equivalent.

Now, as you discovered, sometimes an &mut T to the "inside" of an interior mutable data structure can alias an &Foo<T> to the "outside" of the same data structure. This can happen with GhostCell, but also with RefCell. Then once you did that you can turn the inner &mut T into an &mut UnsafeCell<T>, but that changes nothing. This is no problem as this can only happen when the "outer" shared reference is not actually usable to access any data.

Having aliasing &UnsafeCell<T> and &mut UnsafeCell<T> that are actually both be used to access the underlying data is UB, but that is not what happens here.

@RalfJung
Copy link
Member

I feel like that either this is unsound or the rust aliasing model is fundamentally incompatible with LLVM's noalias.

Neither is the case. LLVM noalias is about pointer accesses, it says nothing about aliasing of pointers that are not actually used to perform loads or stores.

@RalfJung
Copy link
Member

I'm going to close this -- UnsafeCell does not do anything on &mut, but of course it is possible to derive an &mut from an &UnsafeCell (like RefCell::borrow_mut does, and that's also what happens in the GhostCell example in the OP).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants