Auto merge of #125740 - RalfJung:transmute-size-check, r=oli-obk

transmute size check: properly account for alignment

Fixes another place where ZST alignment was ignored when checking whether something is a newtype. I wonder how many more of these there are...

Fixes https://github.com/rust-lang/rust/issues/101084
This commit is contained in:
bors 2024-06-25 07:21:17 +00:00
commit bda221a0eb
6 changed files with 69 additions and 12 deletions

View File

@ -73,7 +73,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Special-case transmuting from `typeof(function)` and
// `Option<typeof(function)>` to present a clearer error.
let from = unpack_option_like(tcx, from);
if let (&ty::FnDef(..), SizeSkeleton::Known(size_to)) = (from.kind(), sk_to)
if let (&ty::FnDef(..), SizeSkeleton::Known(size_to, _)) = (from.kind(), sk_to)
&& size_to == Pointer(dl.instruction_address_space).size(&tcx)
{
struct_span_code_err!(tcx.dcx(), span, E0591, "can't transmute zero-sized type")
@ -88,7 +88,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Try to display a sensible error with as much information as possible.
let skeleton_string = |ty: Ty<'tcx>, sk: Result<_, &_>| match sk {
Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"),
Ok(SizeSkeleton::Known(size)) => {
Ok(SizeSkeleton::Known(size, _)) => {
if let Some(v) = u128::from(size.bytes()).checked_mul(8) {
format!("{v} bits")
} else {

View File

@ -309,7 +309,8 @@ impl<'tcx> LayoutCalculator for LayoutCx<'tcx, TyCtxt<'tcx>> {
#[derive(Copy, Clone, Debug)]
pub enum SizeSkeleton<'tcx> {
/// Any statically computable Layout.
Known(Size),
/// Alignment can be `None` if unknown.
Known(Size, Option<Align>),
/// This is a generic const expression (i.e. N * 2), which may contain some parameters.
/// It must be of type usize, and represents the size of a type in bytes.
@ -339,7 +340,12 @@ impl<'tcx> SizeSkeleton<'tcx> {
// First try computing a static layout.
let err = match tcx.layout_of(param_env.and(ty)) {
Ok(layout) => {
return Ok(SizeSkeleton::Known(layout.size));
if layout.abi.is_sized() {
return Ok(SizeSkeleton::Known(layout.size, Some(layout.align.abi)));
} else {
// Just to be safe, don't claim a known layout for unsized types.
return Err(tcx.arena.alloc(LayoutError::Unknown(ty)));
}
}
Err(err @ LayoutError::Unknown(_)) => err,
// We can't extract SizeSkeleton info from other layout errors
@ -389,19 +395,20 @@ impl<'tcx> SizeSkeleton<'tcx> {
ty::Array(inner, len) if tcx.features().transmute_generic_consts => {
let len_eval = len.try_eval_target_usize(tcx, param_env);
if len_eval == Some(0) {
return Ok(SizeSkeleton::Known(Size::from_bytes(0)));
return Ok(SizeSkeleton::Known(Size::from_bytes(0), None));
}
match SizeSkeleton::compute(inner, tcx, param_env)? {
// This may succeed because the multiplication of two types may overflow
// but a single size of a nested array will not.
SizeSkeleton::Known(s) => {
SizeSkeleton::Known(s, a) => {
if let Some(c) = len_eval {
let size = s
.bytes()
.checked_mul(c)
.ok_or_else(|| &*tcx.arena.alloc(LayoutError::SizeOverflow(ty)))?;
return Ok(SizeSkeleton::Known(Size::from_bytes(size)));
// Alignment is unchanged by arrays.
return Ok(SizeSkeleton::Known(Size::from_bytes(size), a));
}
Err(tcx.arena.alloc(LayoutError::Unknown(ty)))
}
@ -427,8 +434,10 @@ impl<'tcx> SizeSkeleton<'tcx> {
for field in fields {
let field = field?;
match field {
SizeSkeleton::Known(size) => {
if size.bytes() > 0 {
SizeSkeleton::Known(size, align) => {
let is_1zst = size.bytes() == 0
&& align.is_some_and(|align| align.bytes() == 1);
if !is_1zst {
return Err(err);
}
}
@ -492,7 +501,7 @@ impl<'tcx> SizeSkeleton<'tcx> {
pub fn same_size(self, other: SizeSkeleton<'tcx>) -> bool {
match (self, other) {
(SizeSkeleton::Known(a), SizeSkeleton::Known(b)) => a == b,
(SizeSkeleton::Known(a, _), SizeSkeleton::Known(b, _)) => a == b,
(SizeSkeleton::Pointer { tail: a, .. }, SizeSkeleton::Pointer { tail: b, .. }) => {
a == b
}

View File

@ -23,7 +23,7 @@ LL | const C: S = unsafe { std::mem::transmute(()) };
| ^^^^^^^^^^^^^^^^^^^
|
= note: source type: `()` (0 bits)
= note: target type: `S` (this type does not have a fixed size)
= note: target type: `S` (size can vary because of [u8])
error: aborting due to 2 previous errors

View File

@ -28,4 +28,24 @@ unsafe fn specializable<T>(x: u16) -> <T as Specializable>::Output {
//~^ ERROR cannot transmute between types of different sizes, or dependently-sized types
}
#[repr(align(32))]
struct OverAlignZST;
pub struct PtrAndOverAlignZST<T: ?Sized> {
_inner: *mut T,
_other: OverAlignZST,
}
pub unsafe fn shouldnt_work<T: ?Sized>(from: *mut T) -> PtrAndOverAlignZST<T> {
transmute(from)
//~^ ERROR cannot transmute between types of different sizes, or dependently-sized types
}
pub struct PtrAndEmptyArray<T: ?Sized> {
_inner: *mut T,
_other: [*mut T; 0],
}
pub unsafe fn shouldnt_work2<T: ?Sized>(from: *mut T) -> PtrAndEmptyArray<T> {
std::mem::transmute(from)
//~^ ERROR cannot transmute between types of different sizes, or dependently-sized types
}
fn main() {}

View File

@ -25,6 +25,24 @@ LL | transmute(x)
= note: source type: `u16` (N bits)
= note: target type: `<T as Specializable>::Output` (this type does not have a fixed size)
error: aborting due to 3 previous errors
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
--> $DIR/transmute-different-sizes.rs:38:5
|
LL | transmute(from)
| ^^^^^^^^^
|
= note: source type: `*mut T` (pointer to `T`)
= note: target type: `PtrAndOverAlignZST<T>` (size can vary because of <T as Pointee>::Metadata)
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
--> $DIR/transmute-different-sizes.rs:47:5
|
LL | std::mem::transmute(from)
| ^^^^^^^^^^^^^^^^^^^
|
= note: source type: `*mut T` (pointer to `T`)
= note: target type: `PtrAndEmptyArray<T>` (size can vary because of <T as Pointee>::Metadata)
error: aborting due to 5 previous errors
For more information about this error, try `rustc --explain E0512`.

View File

@ -24,11 +24,21 @@ unsafe fn cast_zst3<T>(from: ()) -> [[T; 0]; 8] {
::std::mem::transmute::<(), [[T; 0]; 8]>(from)
}
// Verify transmute with an extra ZST field
pub struct PtrAndZst<T: ?Sized> {
_inner: *mut T,
_other: (),
}
pub unsafe fn cast_ptr<T: ?Sized>(from: *mut T) -> PtrAndZst<T> {
std::mem::transmute(from)
}
pub fn main() {
unsafe {
let _: [u32; 0] = cast_zst0(());
let _ = cast_zst1::<u32>([]);
let _: [(u32, u32); 0] = cast_zst2(());
let _: [[u32; 0]; 8] = cast_zst3(());
cast_ptr(&mut 42);
};
}