Skip to content

Teach transmute_{ref,mut}! to handle slice DSTs #2428

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions src/doctests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2025 The Fuchsia Authors
//
// Licensed under the 2-Clause BSD License <LICENSE-BSD or
// https://opensource.org/license/bsd-2-clause>, Apache License, Version 2.0
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
// This file may not be copied, modified, or distributed except according to
// those terms.

#![cfg(feature = "derive")] // Required for derives on `SliceDst`
#![allow(dead_code)]

//! Our UI test framework, built on the `trybuild` crate, does not support
//! testing for post-monomorphization errors. Instead, we use doctests, which
//! are able to test for post-monomorphization errors.

use crate::*;

#[derive(KnownLayout, FromBytes, IntoBytes, Immutable)]
#[repr(C)]
#[allow(missing_debug_implementations, missing_copy_implementations)]
pub struct SliceDst<T, U> {
pub t: T,
pub u: [U],
}

#[allow(clippy::must_use_candidate, clippy::missing_inline_in_public_items, clippy::todo)]
impl<T: FromBytes + IntoBytes, U: FromBytes + IntoBytes> SliceDst<T, U> {
pub fn new() -> &'static SliceDst<T, U> {
todo!()
}

pub fn new_mut() -> &'static mut SliceDst<T, U> {
todo!()
}
}

/// `transmute_ref!` does not support transmuting from a type of smaller
/// alignment to one of larger alignment.
///
/// ```compile_fail,E0080
/// let increase_alignment: &u16 = zerocopy::transmute_ref!(&[0u8; 2]);
/// ```
///
/// ```compile_fail,E0080
/// let mut src = [0u8; 2];
/// let increase_alignment: &mut u16 = zerocopy::transmute_mut!(&mut src);
/// ```
enum TransmuteRefMutAlignmentIncrease {}

/// We require that the size of the destination type is not larger than the size
/// of the source type.
///
/// ```compile_fail,E0080
/// let increase_size: &[u8; 2] = zerocopy::transmute_ref!(&0u8);
/// ```
///
/// ```compile_fail,E0080
/// let mut src = 0u8;
/// let increase_size: &mut [u8; 2] = zerocopy::transmute_mut!(&mut src);
/// ```
enum TransmuteRefMutSizeIncrease {}

/// We require that the size of the destination type is not smaller than the
/// size of the source type.
///
/// ```compile_fail,E0080
/// let decrease_size: &u8 = zerocopy::transmute_ref!(&[0u8; 2]);
/// ```
///
/// ```compile_fail,E0080
/// let mut src = [0u8; 2];
/// let decrease_size: &mut u8 = zerocopy::transmute_mut!(&mut src);
/// ```
enum TransmuteRefMutSizeDecrease {}

/// It's not possible in the general case to increase the trailing slice offset
/// during a reference transmutation - some pointer metadata values would not be
/// supportable, and so such a transmutation would be fallible.
///
/// ```compile_fail,E0080
/// use zerocopy::doctests::SliceDst;
/// let src: &SliceDst<u8, u8> = SliceDst::new();
/// let increase_offset: &SliceDst<[u8; 2], u8> = zerocopy::transmute_ref!(src);
/// ```
///
/// ```compile_fail,E0080
/// use zerocopy::doctests::SliceDst;
/// let src: &mut SliceDst<u8, u8> = SliceDst::new_mut();
/// let increase_offset: &mut SliceDst<[u8; 2], u8> = zerocopy::transmute_mut!(src);
/// ```
enum TransmuteRefMutDstOffsetIncrease {}

/// Reference transmutes are not possible when the difference between the source
/// and destination types' trailing slice offsets is not a multiple of the
/// destination type's trailing slice element size.
///
/// ```compile_fail,E0080
/// use zerocopy::doctests::SliceDst;
/// let src: &SliceDst<[u8; 3], [u8; 2]> = SliceDst::new();
/// let _: &SliceDst<[u8; 2], [u8; 2]> = zerocopy::transmute_ref!(src);
/// ```
///
/// ```compile_fail,E0080
/// use zerocopy::doctests::SliceDst;
/// let src: &mut SliceDst<[u8; 3], [u8; 2]> = SliceDst::new_mut();
/// let _: &mut SliceDst<[u8; 2], [u8; 2]> = zerocopy::transmute_mut!(src);
/// ```
enum TransmuteRefMutDstOffsetNotMultiple {}

/// Reference transmutes are not possible when the source's trailing slice
/// element size is not a multiple of the destination's.
///
/// ```compile_fail,E0080
/// use zerocopy::doctests::SliceDst;
/// let src: &SliceDst<(), [u8; 3]> = SliceDst::new();
/// let _: &SliceDst<(), [u8; 2]> = zerocopy::transmute_ref!(src);
/// ```
///
/// ```compile_fail,E0080
/// use zerocopy::doctests::SliceDst;
/// let src: &mut SliceDst<(), [u8; 3]> = SliceDst::new_mut();
/// let _: &mut SliceDst<(), [u8; 2]> = zerocopy::transmute_mut!(src);
/// ```
enum TransmuteRefMutDstElemSizeNotMultiple {}
64 changes: 23 additions & 41 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,40 +173,13 @@ const _: () = unsafe {
})
};

// SAFETY: `str` and `[u8]` have the same layout [1].
//
// [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#str-layout:
//
// String slices are a UTF-8 representation of characters that have the same
// layout as slices of type `[u8]`.
unsafe impl pointer::SizeEq<str> for [u8] {
fn cast_from_raw(s: NonNull<str>) -> NonNull<[u8]> {
cast!(s)
}
}
// SAFETY: See previous safety comment.
unsafe impl pointer::SizeEq<[u8]> for str {
fn cast_from_raw(bytes: NonNull<[u8]>) -> NonNull<str> {
cast!(bytes)
}
}
impl_size_eq!(str, [u8]);

macro_rules! unsafe_impl_try_from_bytes_for_nonzero {
($($nonzero:ident[$prim:ty]),*) => {
$(
unsafe_impl!(=> TryFromBytes for $nonzero; |n| {
// SAFETY: The caller promises that this is sound.
unsafe impl pointer::SizeEq<$nonzero> for Unalign<$prim> {
fn cast_from_raw(n: NonNull<$nonzero>) -> NonNull<Unalign<$prim>> {
cast!(n)
}
}
// SAFETY: The caller promises that this is sound.
unsafe impl pointer::SizeEq<Unalign<$prim>> for $nonzero {
fn cast_from_raw(p: NonNull<Unalign<$prim>>) -> NonNull<$nonzero> {
cast!(p)
}
}
impl_size_eq!($nonzero, Unalign<$prim>);

let n = n.transmute::<Unalign<$prim>, invariant::Valid, _>();
$nonzero::new(n.read_unaligned().into_inner()).is_some()
Expand Down Expand Up @@ -423,8 +396,8 @@ mod atomics {
($($($tyvar:ident)? => $atomic:ty [$prim:ty]),*) => {{
crate::util::macros::__unsafe();

use core::{cell::UnsafeCell, ptr::NonNull};
use crate::pointer::{TransmuteFrom, SizeEq, invariant::Valid};
use core::cell::UnsafeCell;
use crate::pointer::{PtrInner, SizeEq, TransmuteFrom, invariant::Valid};

$(
// SAFETY: The caller promised that `$atomic` and `$prim` have
Expand All @@ -437,15 +410,20 @@ mod atomics {
// SAFETY: The caller promised that `$atomic` and `$prim` have
// the same size.
unsafe impl<$($tyvar)?> SizeEq<$atomic> for $prim {
fn cast_from_raw(a: NonNull<$atomic>) -> NonNull<$prim> {
cast!(a)
#[inline]
fn cast_from_raw(a: PtrInner<'_, $atomic>) -> PtrInner<'_, $prim> {
// SAFETY: The caller promised that `$atomic` and
// `$prim` have the same size. Thus, this cast preserves
// address, referent size, and provenance.
unsafe { cast!(a) }
}
}
// SAFETY: The caller promised that `$atomic` and `$prim` have
// the same size.
// SAFETY: See previous safety comment.
unsafe impl<$($tyvar)?> SizeEq<$prim> for $atomic {
fn cast_from_raw(p: NonNull<$prim>) -> NonNull<$atomic> {
cast!(p)
#[inline]
fn cast_from_raw(p: PtrInner<'_, $prim>) -> PtrInner<'_, $atomic> {
// SAFETY: See previous safety comment.
unsafe { cast!(p) }
}
}
// SAFETY: The caller promised that `$atomic` and `$prim` have
Expand All @@ -457,14 +435,18 @@ mod atomics {
// its inner type `T`. A consequence of this guarantee is that
// it is possible to convert between `T` and `UnsafeCell<T>`.
unsafe impl<$($tyvar)?> SizeEq<$atomic> for UnsafeCell<$prim> {
fn cast_from_raw(a: NonNull<$atomic>) -> NonNull<UnsafeCell<$prim>> {
cast!(a)
#[inline]
fn cast_from_raw(a: PtrInner<'_, $atomic>) -> PtrInner<'_, UnsafeCell<$prim>> {
// SAFETY: See previous safety comment.
unsafe { cast!(a) }
}
}
// SAFETY: See previous safety comment.
unsafe impl<$($tyvar)?> SizeEq<UnsafeCell<$prim>> for $atomic {
fn cast_from_raw(p: NonNull<UnsafeCell<$prim>>) -> NonNull<$atomic> {
cast!(p)
#[inline]
fn cast_from_raw(p: PtrInner<'_, UnsafeCell<$prim>>) -> PtrInner<'_, $atomic> {
// SAFETY: See previous safety comment.
unsafe { cast!(p) }
}
}

Expand Down
Loading
Loading