Description
When reading the 1.73.0 release notes, I noticed that the invalid_reference_casting
lint was made deny-by-default in #112431, under the premise that casting &T
to &mut T
via raw pointers is always UB under Stacked Borrows. However, the lint appears a bit overly strict, since such a cast does not cause UB when T
is a zero-sized type:
#![allow(invalid_reference_casting)]
fn main() {
let r1: &() = &();
let r2: &mut () = unsafe { &mut *(r1 as *const _ as *mut _) };
*r2 = ();
}
Without #![allow(invalid_reference_casting)]
, this cast triggers the lint in 1.75.0-nightly (187b813 2023-10-03):
error: casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
--> src/main.rs:5:32
|
5 | let r2: &mut () = unsafe { &mut *(r1 as *const _ as *mut _) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: for more information, visit <https://doc.rust-lang.org/book/ch15-05-interior-mutability.html>
= note: `#[deny(invalid_reference_casting)]` on by default
However, when the lint is disabled, Miri runs the program successfully, under both Stacked Borrows and Tree Borrows. This makes sense looking at the rules: the reference doesn't point to any bytes, so it doesn't need to perform any access or be granted any permissions.
This pattern looks a bit dubious when expressed as a reference cast, but it is not UB. Therefore, I would suggest downgrading or turning off the lint when the destination type is a ZST.
Note: An earlier version of this issue also considered directly casting from &UnsafeCell<i32>
to &mut i32
, which is legal under Stacked Borrows and Tree Borrows, but explicitly called out as UB in the UnsafeCell
documentation. It was decided that linting on this case, as well as the related case of casting from &UnsafeCell<i32>
to &mut UnsafeCell<i32>
which isn't explicitly mentioned, is permissible as an enforcement of library UB until the aliasing rules are ultimately finalized. This was clarified in the diagnostic by #116421.