From 216df4a8e6358a515ba95fb1a92864d1b94c37f3 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 13 Mar 2024 00:11:36 +0000 Subject: [PATCH] safe transmute: require that src referent is smaller than dst The source referent absolutely must be smaller than the destination referent of a ref-to-ref transmute; the excess bytes referenced cannot arise from thin air, even if those bytes are uninitialized. --- .../error_reporting/type_err_ctxt_ext.rs | 7 +++ compiler/rustc_transmute/src/layout/mod.rs | 21 ++++++++ compiler/rustc_transmute/src/layout/tree.rs | 5 +- compiler/rustc_transmute/src/lib.rs | 11 ++++- .../src/maybe_transmutable/mod.rs | 5 ++ .../references/reject_extension.rs | 49 +++++++++++++++++++ .../references/reject_extension.stderr | 25 ++++++++++ .../references/unit-to-u8.stderr | 4 +- 8 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 tests/ui/transmutability/references/reject_extension.rs create mode 100644 tests/ui/transmutability/references/reject_extension.stderr diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs index ad5b7debad7..d18acb8c864 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs @@ -3091,6 +3091,13 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { rustc_transmute::Reason::DstIsTooBig => { format!("The size of `{src}` is smaller than the size of `{dst}`") } + rustc_transmute::Reason::DstRefIsTooBig { src, dst } => { + let src_size = src.size; + let dst_size = dst.size; + format!( + "The referent size of `{src}` ({src_size} bytes) is smaller than that of `{dst}` ({dst_size} bytes)" + ) + } rustc_transmute::Reason::SrcSizeOverflow => { format!( "values of the type `{src}` are too big for the current architecture" diff --git a/compiler/rustc_transmute/src/layout/mod.rs b/compiler/rustc_transmute/src/layout/mod.rs index 0441b49cb14..a7c60c3b490 100644 --- a/compiler/rustc_transmute/src/layout/mod.rs +++ b/compiler/rustc_transmute/src/layout/mod.rs @@ -35,6 +35,8 @@ pub(crate) trait Def: Debug + Hash + Eq + PartialEq + Copy + Clone { pub trait Ref: Debug + Hash + Eq + PartialEq + Copy + Clone { fn min_align(&self) -> usize; + fn size(&self) -> usize; + fn is_mutable(&self) -> bool; } @@ -48,6 +50,9 @@ impl Ref for ! { fn min_align(&self) -> usize { unreachable!() } + fn size(&self) -> usize { + unreachable!() + } fn is_mutable(&self) -> bool { unreachable!() } @@ -57,6 +62,7 @@ impl Ref for ! { pub mod rustc { use rustc_middle::mir::Mutability; use rustc_middle::ty::{self, Ty}; + use std::fmt::{self, Write}; /// A reference in the layout. #[derive(Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Clone, Copy)] @@ -65,6 +71,7 @@ pub mod rustc { pub ty: Ty<'tcx>, pub mutability: Mutability, pub align: usize, + pub size: usize, } impl<'tcx> super::Ref for Ref<'tcx> { @@ -72,6 +79,10 @@ pub mod rustc { self.align } + fn size(&self) -> usize { + self.size + } + fn is_mutable(&self) -> bool { match self.mutability { Mutability::Mut => true, @@ -81,6 +92,16 @@ pub mod rustc { } impl<'tcx> Ref<'tcx> {} + impl<'tcx> fmt::Display for Ref<'tcx> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_char('&')?; + if self.mutability == Mutability::Mut { + f.write_str("mut ")?; + } + self.ty.fmt(f) + } + } + /// A visibility node in the layout. #[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)] pub enum Def<'tcx> { diff --git a/compiler/rustc_transmute/src/layout/tree.rs b/compiler/rustc_transmute/src/layout/tree.rs index 71b72828e4c..c2fc55542ff 100644 --- a/compiler/rustc_transmute/src/layout/tree.rs +++ b/compiler/rustc_transmute/src/layout/tree.rs @@ -372,12 +372,15 @@ pub(crate) mod rustc { } ty::Ref(lifetime, ty, mutability) => { - let align = layout_of(tcx, *ty)?.align(); + let layout = layout_of(tcx, *ty)?; + let align = layout.align(); + let size = layout.size(); Ok(Tree::Ref(Ref { lifetime: *lifetime, ty: *ty, mutability: *mutability, align, + size, })) } diff --git a/compiler/rustc_transmute/src/lib.rs b/compiler/rustc_transmute/src/lib.rs index fefce2640eb..8f3af491453 100644 --- a/compiler/rustc_transmute/src/lib.rs +++ b/compiler/rustc_transmute/src/lib.rs @@ -23,7 +23,7 @@ pub struct Assume { #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub enum Answer { Yes, - No(Reason), + No(Reason), If(Condition), } @@ -42,7 +42,7 @@ pub enum Condition { /// Answers "why wasn't the source type transmutable into the destination type?" #[derive(Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Clone)] -pub enum Reason { +pub enum Reason { /// The layout of the source type is unspecified. SrcIsUnspecified, /// The layout of the destination type is unspecified. @@ -53,6 +53,13 @@ pub enum Reason { DstMayHaveSafetyInvariants, /// `Dst` is larger than `Src`, and the excess bytes were not exclusively uninitialized. DstIsTooBig, + /// A referent of `Dst` is larger than a referent in `Src`. + DstRefIsTooBig { + /// The referent of the source type. + src: T, + /// The too-large referent of the destination type. + dst: T, + }, /// Src should have a stricter alignment than Dst, but it does not. DstHasStricterAlignment { src_min_align: usize, dst_min_align: usize }, /// Can't go from shared pointer to unique pointer diff --git a/compiler/rustc_transmute/src/maybe_transmutable/mod.rs b/compiler/rustc_transmute/src/maybe_transmutable/mod.rs index 0e05aa4d3b2..e9f425686c4 100644 --- a/compiler/rustc_transmute/src/maybe_transmutable/mod.rs +++ b/compiler/rustc_transmute/src/maybe_transmutable/mod.rs @@ -266,6 +266,11 @@ where src_min_align: src_ref.min_align(), dst_min_align: dst_ref.min_align(), }) + } else if dst_ref.size() > src_ref.size() { + Answer::No(Reason::DstRefIsTooBig { + src: src_ref, + dst: dst_ref, + }) } else { // ...such that `src` is transmutable into `dst`, if // `src_ref` is transmutability into `dst_ref`. diff --git a/tests/ui/transmutability/references/reject_extension.rs b/tests/ui/transmutability/references/reject_extension.rs new file mode 100644 index 00000000000..161da5772e8 --- /dev/null +++ b/tests/ui/transmutability/references/reject_extension.rs @@ -0,0 +1,49 @@ +//@ check-fail + +//! Reject extensions behind references. + +#![crate_type = "lib"] +#![feature(transmutability)] + +mod assert { + use std::mem::{Assume, BikeshedIntrinsicFrom}; + + pub fn is_transmutable() + where + Dst: BikeshedIntrinsicFrom< + Src, + { + Assume { + alignment: true, + lifetimes: true, + safety: true, + validity: true, + } + }, + >, + { + } +} + +#[repr(C, packed)] +struct Packed(T); + +fn reject_extension() { + #[repr(C, align(2))] + struct Two(u8); + + #[repr(C, align(4))] + struct Four(u8); + + // These two types differ in the number of trailing padding bytes they have. + type Src = Packed; + type Dst = Packed; + + const _: () = { + use std::mem::size_of; + assert!(size_of::() == 2); + assert!(size_of::() == 4); + }; + + assert::is_transmutable::<&Src, &Dst>(); //~ ERROR cannot be safely transmuted +} diff --git a/tests/ui/transmutability/references/reject_extension.stderr b/tests/ui/transmutability/references/reject_extension.stderr new file mode 100644 index 00000000000..e02ef89c4a0 --- /dev/null +++ b/tests/ui/transmutability/references/reject_extension.stderr @@ -0,0 +1,25 @@ +error[E0277]: `&Packed` cannot be safely transmuted into `&Packed` + --> $DIR/reject_extension.rs:48:37 + | +LL | assert::is_transmutable::<&Src, &Dst>(); + | ^^^^ The referent size of `&Packed` (2 bytes) is smaller than that of `&Packed` (4 bytes) + | +note: required by a bound in `is_transmutable` + --> $DIR/reject_extension.rs:13:14 + | +LL | pub fn is_transmutable() + | --------------- required by a bound in this function +LL | where +LL | Dst: BikeshedIntrinsicFrom< + | ______________^ +LL | | Src, +LL | | { +LL | | Assume { +... | +LL | | }, +LL | | >, + | |_________^ required by this bound in `is_transmutable` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/transmutability/references/unit-to-u8.stderr b/tests/ui/transmutability/references/unit-to-u8.stderr index e2eb50442ca..7cb45e24e0a 100644 --- a/tests/ui/transmutability/references/unit-to-u8.stderr +++ b/tests/ui/transmutability/references/unit-to-u8.stderr @@ -1,8 +1,8 @@ -error[E0277]: `Unit` cannot be safely transmuted into `u8` +error[E0277]: `&Unit` cannot be safely transmuted into `&u8` --> $DIR/unit-to-u8.rs:22:52 | LL | assert::is_maybe_transmutable::<&'static Unit, &'static u8>(); - | ^^^^^^^^^^^ The size of `Unit` is smaller than the size of `u8` + | ^^^^^^^^^^^ The referent size of `&Unit` (0 bytes) is smaller than that of `&u8` (1 bytes) | note: required by a bound in `is_maybe_transmutable` --> $DIR/unit-to-u8.rs:9:14