Skip to content

Commit

Permalink
transmute size check: properly account for alignment
Browse files Browse the repository at this point in the history
  • Loading branch information
RalfJung committed May 30, 2024
1 parent a83f933 commit 9f0c7f0
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 12 deletions.
4 changes: 2 additions & 2 deletions compiler/rustc_hir_typeck/src/intrinsicck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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 {
Expand Down
25 changes: 17 additions & 8 deletions compiler/rustc_middle/src/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,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.
Expand Down Expand Up @@ -338,7 +339,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
Expand Down Expand Up @@ -390,19 +396,20 @@ impl<'tcx> SizeSkeleton<'tcx> {
{
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)))
}
Expand All @@ -428,8 +435,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);
}
}
Expand Down Expand Up @@ -493,7 +502,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
}
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/layout/base-layout-is-sized-ice-123078.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
20 changes: 20 additions & 0 deletions tests/ui/transmute/transmute-different-sizes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
20 changes: 19 additions & 1 deletion tests/ui/transmute/transmute-different-sizes.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
10 changes: 10 additions & 0 deletions tests/ui/transmute/transmute-zst-generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
}

0 comments on commit 9f0c7f0

Please sign in to comment.