Rollup merge of #122438 - jswrenn:check-referent-size, r=compiler-errors

Safe Transmute: Require that source referent is smaller than destination

`BikeshedIntrinsicFrom` currently models transmute-via-union; i.e., it attempts to provide a `where` bound for this function:
```rust
pub unsafe fn transmute_via_union<Src, Dst>(src: Src) -> Dst {
    use core::mem::*;

    #[repr(C)]
    union Transmute<T, U> {
        src: ManuallyDrop<T>,
        dst: ManuallyDrop<U>,
    }

    let transmute = Transmute { src: ManuallyDrop::new(src) };

    // SAFETY: The caller must guarantee that the transmutation is safe.
    let dst = transmute.dst;

    ManuallyDrop::into_inner(dst)
}
```
A quirk of this model is that it admits padding extensions in value-to-value transmutation: The destination type can be bigger than the source type, so long as the excess consists of uninitialized bytes. However, this isn't permissible for reference-to-reference transmutations (introduced in #110662) — extra referent bytes cannot come from thin air.

This PR patches our analysis for reference-to-reference transmutations to require that the destination referent is no larger than the source referent.

r? `@compiler-errors`
This commit is contained in:
Matthias Krüger 2024-03-13 20:01:58 +01:00 committed by GitHub
commit 89c3fa92d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 122 additions and 5 deletions

View File

@ -3091,6 +3091,13 @@ fn get_safe_transmute_error_and_reason(
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"

View File

@ -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 @@ fn is_mutable(&self) -> bool {
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 struct Ref<'tcx> {
pub ty: Ty<'tcx>,
pub mutability: Mutability,
pub align: usize,
pub size: usize,
}
impl<'tcx> super::Ref for Ref<'tcx> {
@ -72,6 +79,10 @@ fn min_align(&self) -> usize {
self.align
}
fn size(&self) -> usize {
self.size
}
fn is_mutable(&self) -> bool {
match self.mutability {
Mutability::Mut => true,
@ -81,6 +92,16 @@ fn is_mutable(&self) -> bool {
}
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> {

View File

@ -372,12 +372,15 @@ pub fn from_ty(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Result<Self, Err> {
}
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,
}))
}

View File

@ -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

View File

@ -266,6 +266,11 @@ fn answer_memo(
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`.

View File

@ -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
}

View File

@ -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`.

View File

@ -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