diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index 828572df6968b..79a8dfc057400 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; @@ -1077,6 +1078,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 /// /// ``` @@ -1101,20 +1114,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/codegen-llvm/lib-optimizations/transmute_copy.rs b/tests/codegen-llvm/lib-optimizations/transmute_copy.rs new file mode 100644 index 0000000000000..bc96fd08c74aa --- /dev/null +++ b/tests/codegen-llvm/lib-optimizations/transmute_copy.rs @@ -0,0 +1,65 @@ +//@ compile-flags: -Copt-level=1 +//@ only-64bit + +#![crate_type = "lib"] + +// Check that we don't have runtime alignment checks, just a safe alignment. +// (Rust emits a branch, but LLVM merges the loads from the arms.) + +use std::mem::transmute_copy; + +// CHECK-LABEL: @transmute_copy_i16_from_i32_slice +// CHECK-SAME: ptr{{.+}}%_0, +// CHECK-SAME: ptr{{.+}}%x.0, +// CHECK-SAME: i64{{.+}}%x.1) +#[no_mangle] +pub unsafe fn transmute_copy_i16_from_i32_slice(x: &[i32]) -> [i16; 7] { + // CHECK: start: + // CHECK-NEXT: @llvm.memcpy + // CHECK-SAME: align 2{{.+}}%_0, + // CHECK-SAME: align 4{{.+}}%x.0, + // CHECK-SAME: i64 14, + // CHECK-NEXT: ret void + transmute_copy(x) +} + +// CHECK-LABEL: @transmute_copy_i32_from_i16_slice +// CHECK-SAME: ptr{{.+}}%_0, +// CHECK-SAME: ptr{{.+}}%x.0, +// CHECK-SAME: i64{{.+}}%x.1) +#[no_mangle] +pub unsafe fn transmute_copy_i32_from_i16_slice(x: &[i16]) -> [i32; 3] { + // CHECK: start: + // CHECK-NEXT: @llvm.memcpy + // CHECK-SAME: align 4{{.+}}%_0, + // CHECK-SAME: align 2{{.+}}%x.0, + // CHECK-SAME: i64 12, + // CHECK-NEXT: ret void + transmute_copy(x) +} + +// CHECK-LABEL: i32 @transmute_copy_i32_from_dyn +// CHECK-SAME: ptr{{.+}}%x.0, +// CHECK-SAME: ptr{{.+}}%x.1) +#[no_mangle] +pub unsafe fn transmute_copy_i32_from_dyn(x: &dyn std::fmt::Debug) -> i32 { + // CHECK: start: + // CHECK-NEXT: [[TEMP:%.+]] = load i32, ptr %x.0, align 1 + // CHECK-NEXT: ret i32 [[TEMP]] + transmute_copy(x) +} + +// CHECK-LABEL: @transmute_copy_i16_array_from_dyn +// CHECK-SAME: ptr{{.+}}%_0, +// CHECK-SAME: ptr{{.+}}%x.0, +// CHECK-SAME: ptr{{.+}}%x.1) +#[no_mangle] +pub unsafe fn transmute_copy_i16_array_from_dyn(x: &dyn std::fmt::Debug) -> [i16; 7] { + // CHECK: start: + // CHECK-NEXT: @llvm.memcpy + // CHECK-SAME: align 2{{.+}}%_0, + // CHECK-SAME: align 1{{.+}}%x.0, + // CHECK-SAME: i64 14, + // CHECK-NEXT: ret void + transmute_copy(x) +} 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); + } +}