about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJack Wrenn <jack@wrenn.fyi>2024-03-13 00:11:36 +0000
committerJack Wrenn <jack@wrenn.fyi>2024-03-13 15:53:48 +0000
commit216df4a8e6358a515ba95fb1a92864d1b94c37f3 (patch)
tree8bfa6be348f0cca0fc53705def0763e3647544b9
parenta165f1f65015b1bd4afd2ec50700aaacf2e0c485 (diff)
downloadrust-216df4a8e6358a515ba95fb1a92864d1b94c37f3.tar.gz
rust-216df4a8e6358a515ba95fb1a92864d1b94c37f3.zip
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.
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs7
-rw-r--r--compiler/rustc_transmute/src/layout/mod.rs21
-rw-r--r--compiler/rustc_transmute/src/layout/tree.rs5
-rw-r--r--compiler/rustc_transmute/src/lib.rs11
-rw-r--r--compiler/rustc_transmute/src/maybe_transmutable/mod.rs5
-rw-r--r--tests/ui/transmutability/references/reject_extension.rs49
-rw-r--r--tests/ui/transmutability/references/reject_extension.stderr25
-rw-r--r--tests/ui/transmutability/references/unit-to-u8.stderr4
8 files changed, 122 insertions, 5 deletions
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<R> {
     Yes,
-    No(Reason),
+    No(Reason<R>),
     If(Condition<R>),
 }
 
@@ -42,7 +42,7 @@ pub enum Condition<R> {
 
 /// 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<T> {
     /// 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<Src, Dst>()
+    where
+        Dst: BikeshedIntrinsicFrom<
+            Src,
+            {
+                Assume {
+                    alignment: true,
+                    lifetimes: true,
+                    safety: true,
+                    validity: true,
+                }
+            },
+        >,
+    {
+    }
+}
+
+#[repr(C, packed)]
+struct Packed<T>(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<Two>;
+    type Dst = Packed<Four>;
+
+    const _: () = {
+        use std::mem::size_of;
+        assert!(size_of::<Src>() == 2);
+        assert!(size_of::<Dst>() == 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<Two>` cannot be safely transmuted into `&Packed<Four>`
+  --> $DIR/reject_extension.rs:48:37
+   |
+LL |     assert::is_transmutable::<&Src, &Dst>();
+   |                                     ^^^^ The referent size of `&Packed<Two>` (2 bytes) is smaller than that of `&Packed<Four>` (4 bytes)
+   |
+note: required by a bound in `is_transmutable`
+  --> $DIR/reject_extension.rs:13:14
+   |
+LL |       pub fn is_transmutable<Src, Dst>()
+   |              --------------- 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