diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index 2fbb5842ef778..77196708437b8 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -35,6 +35,7 @@ use crate::clone::TrivialClone; use crate::cmp::Ordering; use crate::marker::{Destruct, DiscriminantKind}; use crate::panic::const_assert; +use crate::ub_checks::assert_unsafe_precondition; use crate::{clone, cmp, fmt, hash, intrinsics, ptr}; mod alignment; @@ -1040,6 +1041,18 @@ pub const fn copy(x: &T) -> T { /// /// [ub]: ../../reference/behavior-considered-undefined.html /// +/// If you have a raw pointer instead of a reference, you might be looking for +/// [`ptr::read_unaligned`]`::(ptr.cast::())` instead. +/// +/// # Safety +/// +/// - Requires `size_of_val::(src) >= size_of::()` +/// - The first `size_of::()` bytes behind `src` must be *readable* +/// - The first `size_of::()` bytes behind `src` must be *valid* +/// when interpreted as a `Dst`. +/// - All safety invariants of the `Dst` type must be upheld. (For example, +/// `{ transmute_copy::(&x); x; }` is UB for the double-drop.) +/// /// # Examples /// /// ``` @@ -1064,20 +1077,32 @@ pub const fn copy(x: &T) -> T { /// /// // The contents of 'foo_array' should not have changed /// assert_eq!(foo_array, [10]); +/// +/// let bytes: &[u8] = &[1, 2, 3, 4, 5, 6, 7]; +/// assert_eq!( +/// unsafe { mem::transmute_copy::<[u8], u32>(bytes) }, +/// u32::from_ne_bytes(*bytes.first_chunk().unwrap()), +/// ); /// ``` #[inline] #[must_use] #[track_caller] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_stable(feature = "const_transmute_copy", since = "1.74.0")] -pub const unsafe fn transmute_copy(src: &Src) -> Dst { - assert!( - size_of::() >= size_of::(), - "cannot transmute_copy if Dst is larger than Src" +pub const unsafe fn transmute_copy(src: &Src) -> Dst { + // library UB because it's possible for the `Src` to be only a subset of the allocation + // and thus for a failure to not be immediate language UB + assert_unsafe_precondition!( + check_library_ub, + "cannot transmute_copy if Dst is larger than Src", + ( + src_size: usize = size_of_val::(src), + dst_size: usize = Dst::SIZE, + ) => src_size >= dst_size ); // If Dst has a higher alignment requirement, src might not be suitably aligned. - if align_of::() > align_of::() { + if align_of::() > align_of_val::(src) { // SAFETY: `src` is a reference which is guaranteed to be valid for reads. // The caller must guarantee that the actual transmutation is safe. unsafe { ptr::read_unaligned(src as *const Src as *const Dst) } diff --git a/library/coretests/tests/mem.rs b/library/coretests/tests/mem.rs index b95e6e13063f5..793c9f489e47f 100644 --- a/library/coretests/tests/mem.rs +++ b/library/coretests/tests/mem.rs @@ -138,32 +138,6 @@ fn test_transmute_copy_unaligned() { assert_eq!(0_u64, unsafe { transmute_copy(&u.b) }); } -#[test] -#[cfg(panic = "unwind")] -fn test_transmute_copy_grow_panics() { - use std::panic; - - let err = panic::catch_unwind(panic::AssertUnwindSafe(|| unsafe { - let _unused: u64 = transmute_copy(&1_u8); - })); - - match err { - Ok(_) => unreachable!(), - Err(payload) => { - payload - .downcast::<&'static str>() - .and_then(|s| { - if *s == "cannot transmute_copy if Dst is larger than Src" { - Ok(s) - } else { - Err(s) - } - }) - .unwrap_or_else(|p| panic::resume_unwind(p)); - } - } -} - #[test] #[allow(dead_code)] fn test_discriminant_send_sync() { diff --git a/tests/ui/precondition-checks/transmute_copy.rs b/tests/ui/precondition-checks/transmute_copy.rs new file mode 100644 index 0000000000000..160699a4c8ccd --- /dev/null +++ b/tests/ui/precondition-checks/transmute_copy.rs @@ -0,0 +1,9 @@ +//@ run-crash +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: cannot transmute_copy if Dst is larger than Src + +fn main() { + unsafe { + let _unused: u64 = std::mem::transmute_copy(&1_u8); + } +}