Skip to content

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

Closed
@SkiFire13

Description

@SkiFire13

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.

Activity

phlopsi

phlopsi commented on May 22, 2021

@phlopsi

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

matthieu-m commented on May 22, 2021

@matthieu-m

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

RalfJung commented on May 22, 2021

@RalfJung
Member

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

RalfJung commented on May 22, 2021

@RalfJung
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

RalfJung commented on Apr 10, 2022

@RalfJung
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

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

        Participants

        @RalfJung@matthieu-m@phlopsi@SkiFire13

        Issue actions

          Can a `&UnsafeCell<T>` and a `&mut UnsafeCell<T>` alias? · Issue #284 · rust-lang/unsafe-code-guidelines