Lint against &T to &mut T and &T to &UnsafeCell<T> transmutes (rebase) by GrigorenkoPV · Pull Request #143343 · rust-lang/rust (original) (raw)

This is a rebase of #128351 with the new lint's name being reverted back to unsafe_cell_transmutes, as requested by T-lang.

Conversion from & to &mut are and always were immediate UB, and we already lint against them, but until now the lint did not catch the case were the reference was in a field.

Conversion from & to &UnsafeCell is more nuanced: Stacked Borrows makes it immediate UB, but in Tree Borrows it is sound.

However, even in Tree Borrows it is UB to write into that reference (if the original value was Freeze). In all cases crater found where the lint triggered, the reference was written into.

Lints (mutable_transmutes existed before):

The mutable_transmutes lint catches transmuting from &T to &mut T because it is undefined behavior.

Example

unsafe { let y = std::mem::transmute::<&i32, &mut i32>(&5); }

Produces:

error: transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell
--> .\example.rs:5:17
 |
5 |         let y = std::mem::transmute::<&i32, &mut i32>(&5);
 |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 |
 = note: transmute from `&i32` to `&mut i32`
 = note: `#[deny(mutable_transmutes)]` on by default

Explanation

Certain assumptions are made about aliasing of data, and this transmute
violates those assumptions. Consider using UnsafeCell instead.

The unsafe_cell_transmutes lint catches transmuting or casting from &T to &UnsafeCell
because it dangerous and might be undefined behavior.

Example

use std::cell::Cell;

unsafe { let x = 5_i32; let y = std::mem::transmute::<&i32, &Cell>(&x); y.set(6);

let z = &*(&x as *const i32 as *const Cell); z.set(7); }

Produces:

error: transmuting &T to &UnsafeCell<T> is error-prone, rarely intentional and may cause undefined behavior
--> .\example.rs:6:17
 |
6 |         let y = std::mem::transmute::<&i32, &Cell<i32>>(&x);
 |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 |
 = note: transmute from `*&i32` to `(*&Cell<i32>).value`
 = note: `#[deny(unsafe_cell_transmutes)]` on by default

error: transmuting &T to &UnsafeCell<T> is error-prone, rarely intentional and may cause undefined behavior
--> .\example.rs:9:17
 |
9 |         let z = &*(&x as *const i32 as *const Cell<i32>);
 |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 |
 = note: transmute from `i32` to `Cell<i32>.value`

Explanation

Conversion from &T to &UnsafeCell<T> might be immediate undefined behavior, depending on
unspecified details of the aliasing model.
Even if it is not, writing to it will be undefined behavior if there was no UnsafeCell in
the original T, and even if there was, it might be undefined behavior (again, depending
on unspecified details of the aliasing model).
It is also highly dangerous and error-prone, and unlikely to be useful.

Crater summary is below.

cc #111229

r? compiler