Miri reports error when retagging UnsafeCell immutably

The following code triggers a Miri error (playground):

fn main() {
    let v = 123;
    let r = &v as *const i32 as *const std::cell::UnsafeCell<i32>;
    unsafe { &*r };
}
error: Undefined Behavior: trying to retag from <3119> for SharedReadWrite permission at alloc1656[0x0], but that tag only grants SharedReadOnly permission for this location
 --> src/main.rs:4:14
  |
4 |     unsafe { &*r };
  |              ^^^
  |              |
  |              trying to retag from <3119> for SharedReadWrite permission at alloc1656[0x0], but that tag only grants SharedReadOnly permission for this location
  |              this error occurs as part of retag at alloc1656[0x0..0x4]
  |
  = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
  = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <3119> was created by a SharedReadOnly retag at offsets [0x0..0x4]
 --> src/main.rs:3:13
  |
3 |     let r = &v as *const i32 as *const std::cell::UnsafeCell<i32>;
  |             ^^
  = note: BACKTRACE (of the first span):
  = note: inside `main` at src/main.rs:4:14: 4:17

It only occurs if we cast to UnsafeCell (or some other type containing it, e.g. Cell). It seems that because of the UnsafeCell, miri treates the immutable borrow as SharedReadWrite, even though we never write into it.

Is this truly a UB or is this a false positive? If a false positive, how can I work around it so Miri won't report it?

You literally took a variable v defined to be immutable and created a &UnsafeCell reference to it, which allows you to get a mutable pointer to v.
This is UB.

2 Likes

This is definitely unsound, if I expose it to safe code. But at the same time, we can write through a mutable raw pointer, and yet it is not UB to create *mut T from &T unless we write into it.

I've had variations of this problem, it seems that the only solution is "absolutely don't do it, even if it feels safe".

Yes, unfortunately the rules are very strict. The rule is not that "you may not write to an immutable memory location". The rule is that "you may not materialize a reference which would theoretically allow you to write to an immutable memory location".

In general there doesn't seem to be any sort of workaround, you just can't do it. If you need to do stuff like this, you must work with pointers, not references. UnsafeCell specifically seems to have raw_get for exactly this purpose (avoid the temporary reference materialization)

4 Likes

The problem is that I'm working with atomics, so raw_get() doesn't help me (I didn't know it was stabilized, though!)... I will have to find a different way.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.