From 9f0c7f00d1d705435fef309576e0522cb0c25497 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 29 May 2024 22:07:56 +0200 Subject: [PATCH] transmute size check: properly account for alignment --- compiler/rustc_hir_typeck/src/intrinsicck.rs | 4 +-- compiler/rustc_middle/src/ty/layout.rs | 25 +++++++++++++------ .../base-layout-is-sized-ice-123078.stderr | 2 +- .../ui/transmute/transmute-different-sizes.rs | 20 +++++++++++++++ .../transmute-different-sizes.stderr | 20 ++++++++++++++- tests/ui/transmute/transmute-zst-generics.rs | 10 ++++++++ 6 files changed, 69 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/intrinsicck.rs b/compiler/rustc_hir_typeck/src/intrinsicck.rs index fb8863c143f..5eafc60a04e 100644 --- a/compiler/rustc_hir_typeck/src/intrinsicck.rs +++ b/compiler/rustc_hir_typeck/src/intrinsicck.rs @@ -73,7 +73,7 @@ pub fn check_transmute(&self, from: Ty<'tcx>, to: Ty<'tcx>, hir_id: HirId) { // Special-case transmuting from `typeof(function)` and // `Option` 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 @@ pub fn check_transmute(&self, from: Ty<'tcx>, to: Ty<'tcx>, hir_id: HirId) { // 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 { diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index 60ce8744032..5c9df6e776a 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -308,7 +308,8 @@ fn current_data_layout(&self) -> Self::TargetDataLayoutRef { #[derive(Copy, Clone, Debug)] pub enum SizeSkeleton<'tcx> { /// Any statically computable Layout. - Known(Size), + /// Alignment can be `None` if unknown. + Known(Size, Option), /// 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. @@ -338,7 +339,12 @@ pub fn compute( // 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 @@ -390,19 +396,20 @@ pub fn compute( { 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))) } @@ -428,8 +435,10 @@ pub fn compute( 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); } } @@ -493,7 +502,7 @@ pub fn compute( 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 } diff --git a/tests/ui/layout/base-layout-is-sized-ice-123078.stderr b/tests/ui/layout/base-layout-is-sized-ice-123078.stderr index 7e0a41a4367..455bd2cbf8b 100644 --- a/tests/ui/layout/base-layout-is-sized-ice-123078.stderr +++ b/tests/ui/layout/base-layout-is-sized-ice-123078.stderr @@ -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 diff --git a/tests/ui/transmute/transmute-different-sizes.rs b/tests/ui/transmute/transmute-different-sizes.rs index 4fe79b9fa4e..ac98eb231dd 100644 --- a/tests/ui/transmute/transmute-different-sizes.rs +++ b/tests/ui/transmute/transmute-different-sizes.rs @@ -28,4 +28,24 @@ unsafe fn specializable(x: u16) -> ::Output { //~^ ERROR cannot transmute between types of different sizes, or dependently-sized types } +#[repr(align(32))] +struct OverAlignZST; +pub struct PtrAndOverAlignZST { + _inner: *mut T, + _other: OverAlignZST, +} +pub unsafe fn shouldnt_work(from: *mut T) -> PtrAndOverAlignZST { + transmute(from) + //~^ ERROR cannot transmute between types of different sizes, or dependently-sized types +} + +pub struct PtrAndEmptyArray { + _inner: *mut T, + _other: [*mut T; 0], +} +pub unsafe fn shouldnt_work2(from: *mut T) -> PtrAndEmptyArray { + std::mem::transmute(from) + //~^ ERROR cannot transmute between types of different sizes, or dependently-sized types +} + fn main() {} diff --git a/tests/ui/transmute/transmute-different-sizes.stderr b/tests/ui/transmute/transmute-different-sizes.stderr index 07a38df6973..ea3a017c16e 100644 --- a/tests/ui/transmute/transmute-different-sizes.stderr +++ b/tests/ui/transmute/transmute-different-sizes.stderr @@ -25,6 +25,24 @@ LL | transmute(x) = note: source type: `u16` (N bits) = note: target type: `::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` (size can vary because of ::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` (size can vary because of ::Metadata) + +error: aborting due to 5 previous errors For more information about this error, try `rustc --explain E0512`. diff --git a/tests/ui/transmute/transmute-zst-generics.rs b/tests/ui/transmute/transmute-zst-generics.rs index 9aeb21923ea..b80ec794f83 100644 --- a/tests/ui/transmute/transmute-zst-generics.rs +++ b/tests/ui/transmute/transmute-zst-generics.rs @@ -24,11 +24,21 @@ ::std::mem::transmute::<(), [[T; 0]; 8]>(from) } +// Verify transmute with an extra ZST field +pub struct PtrAndZst { + _inner: *mut T, + _other: (), +} +pub unsafe fn cast_ptr(from: *mut T) -> PtrAndZst { + std::mem::transmute(from) +} + pub fn main() { unsafe { let _: [u32; 0] = cast_zst0(()); let _ = cast_zst1::([]); let _: [(u32, u32); 0] = cast_zst2(()); let _: [[u32; 0]; 8] = cast_zst3(()); + cast_ptr(&mut 42); }; }