Skip to content

Commit

Permalink
Add transmute_container! macro
Browse files Browse the repository at this point in the history
  • Loading branch information
joshlf committed Sep 27, 2024
1 parent 6b7a012 commit 27dcf55
Show file tree
Hide file tree
Showing 3 changed files with 310 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ zerocopy-core-error = "1.81.0"
# From 1.78.0, Rust supports the `#[diagnostic::on_unimplemented]` attribute.
zerocopy-diagnostic-on-unimplemented = "1.78.0"

# From 1.65.0, Rust supports generic associated types (GATs).
zerocopy-gat = "1.65.0"

# From 1.61.0, Rust supports generic types with trait bounds in `const fn`.
zerocopy-generic-bounds-in-const-fn = "1.61.0"

Expand Down
139 changes: 139 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,110 @@ macro_rules! try_transmute_mut {
}}
}

/// TODO:
/// - Prevent lifetime extension
/// - Justify that this doesn't violate Src or Dst's safety invariants
/// (basically just that they opted into being FromBytes + IntoBytes +
/// Immutable)
#[macro_export]
#[cfg(all(zerocopy_gat, feature = "alloc"))]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
macro_rules! transmute_container {
($e:expr) => {{
// NOTE: This must be a macro (rather than a function with trait bounds)
// because there's no way, in a generic context, to enforce that two
// types have the same size or alignment.

let e = $e;

fn into_elem<C: $crate::util::macro_util::Container>(_c: C) -> C::Elem {
loop {}
}

fn from_elem<C: $crate::util::macro_util::Container>(_e: C::Elem) -> C {
loop {}
}

#[allow(unused, clippy::diverging_sub_expression)]
if false {
// This branch, though never taken, ensures that element type of `e`
// is `T` where `T: Sized + FromBytes + IntoBytes + Immutable` and
// that the element type of this macro expression is `U` where `U:
// Sized + FromBytes + IntoBytes + Immutable`.

struct AssertSrcIsSized<T: ::core::marker::Sized>(T);
struct AssertSrcIsFromBytes<T: ?::core::marker::Sized + $crate::FromBytes>(T);
struct AssertSrcIsIntoBytes<T: ?::core::marker::Sized + $crate::IntoBytes>(T);
struct AssertSrcIsImmutable<T: ?::core::marker::Sized + $crate::Immutable>(T);
struct AssertDstIsSized<T: ::core::marker::Sized>(T);
struct AssertDstIsFromBytes<T: ?::core::marker::Sized + $crate::FromBytes>(T);
struct AssertDstIsIntoBytes<T: ?::core::marker::Sized + $crate::IntoBytes>(T);
struct AssertDstIsImmutable<T: ?::core::marker::Sized + $crate::Immutable>(T);

let src_elem = into_elem(e);

if true {
let _ = AssertSrcIsSized(src_elem);
} else if true {
let _ = AssertSrcIsFromBytes(src_elem);
} else if true {
let _ = AssertSrcIsIntoBytes(src_elem);
} else {
let _ = AssertSrcIsImmutable(src_elem);
}

if true {
#[allow(unused, unreachable_code)]
let u = AssertDstIsSized(loop {});
from_elem(u.0)
} else if true {
#[allow(unused, unreachable_code)]
let u = AssertDstIsFromBytes(loop {});
from_elem(u.0)
} else if true {
#[allow(unused, unreachable_code)]
let u = AssertDstIsIntoBytes(loop {});
from_elem(u.0)
} else {
#[allow(unused, unreachable_code)]
let u = AssertDstIsImmutable(loop {});
from_elem(u.0)
}
} else if false {
// This branch, though never taken, ensures that `size_of::<T>() ==
// size_of::<U>()` and that that `align_of::<T>() >=
// align_of::<U>()`.

// `t` is inferred to have type `T` because it's assigned to `e` (of
// type `&mut T`) as `&mut t`.
let mut t = loop {};
e = from_elem(t);

// `u` is inferred to have type `U` because it's used as `&mut u` as
// the value returned from this branch.
let u;

$crate::assert_size_eq!(t, u);
$crate::assert_align_eq!(t, u);

from_elem(u)
} else {
// SAFETY: For source type `Src` and destination type `Dst`:
// - We know that `size_of::<Src>() == size_of::<Dst>()` thanks to
// the use of `assert_size_eq!` above.
// - We know that `align_of::<Src>() == align_of::<Dst>()` thanks to
// the use of `assert_align_eq!` above.
// - We know that `Src` and `Dst` have equivalent bit validity
// thanks to `Src: FromBytes + IntoBytes` and `Dst: FromBytes +
// IntoBytes`, which are guaranteed above.
// - We know that `Src` and `Dst` have `UnsafeCell`s at the same
// offsets thanks to `Src: Immutable` and `Dst: Immutable` above.
let u = unsafe { $crate::util::macro_util::Container::transmute_from(e) };
$crate::util::macro_util::must_use(u)
}
}}
}

/// Includes a file and safely transmutes it to a value of an arbitrary type.
///
/// The file will be included as a byte array, `[u8; N]`, which will be
Expand Down Expand Up @@ -949,6 +1053,27 @@ mod tests {
assert_eq!(*y, 0);
}

#[test]
#[cfg(all(zerocopy_gat, feature = "alloc"))]
fn test_transmute_container() {
use alloc::{rc::Rc, sync::Arc};

macro_rules! test {
($cont:ident) => {
let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7];
let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]];
let x: $cont<[[u8; 2]; 4]> = transmute_container!($cont::new(array_of_u8s));
assert_eq!(*x, array_of_arrays);
let x: $cont<[u8; 8]> = transmute_container!($cont::new(array_of_arrays));
assert_eq!(*x, array_of_u8s);
};
}

test!(Box);
test!(Rc);
test!(Arc);
}

#[test]
fn test_macros_evaluate_args_once() {
let mut ctr = 0;
Expand Down Expand Up @@ -981,6 +1106,20 @@ mod tests {
})
.unwrap();
assert_eq!(ctr, 1);

let mut ctr = 0;
let _: Result<&usize, _> = try_transmute_ref!({
ctr += 1;
&0usize
});
assert_eq!(ctr, 1);

let mut ctr: usize = 0;
let _: Result<&mut usize, _> = try_transmute_mut!({
ctr += 1;
&mut ctr
});
assert_eq!(ctr, 1);
}

#[test]
Expand Down
168 changes: 168 additions & 0 deletions src/util/macro_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,36 @@ macro_rules! assert_align_gt_eq {
}};
}

#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
#[macro_export]
macro_rules! assert_align_eq {
($t:ident, $u: ident) => {{
// The comments here should be read in the context of this macro's
// invocations in `transmute_ref!` and `transmute_mut!`.
if false {
let align_t: $crate::util::macro_util::AlignOf<_> = unreachable!();
$t = align_t.into_t();

let mut align_u: $crate::util::macro_util::AlignOf<_> = unreachable!();
$u = align_u.into_t();

// This transmute will only compile successfully if
// `align_of::<T>() == max(align_of::<T>(), align_of::<U>())` - in
// other words, if `align_of::<T>() >= align_of::<U>()`.
//
// SAFETY: This code is never run.
align_u = unsafe {
// Clippy: We can't annotate the types; this macro is designed
// to infer the types from the calling context.
#[allow(clippy::missing_transmute_annotations)]
$crate::util::macro_util::core_reexport::mem::transmute(align_t)
};
} else {
loop {}
}
}};
}

/// Do `t` and `u` have the same size? If not, this macro produces a compile
/// error. It must be invoked in a dead codepath. This is used in
/// `transmute_ref!` and `transmute_mut!`.
Expand Down Expand Up @@ -683,6 +713,144 @@ pub const fn must_use<T>(t: T) -> T {
t
}

#[cfg(all(zerocopy_gat, feature = "alloc"))]
mod container {
use alloc::{boxed::Box, rc::Rc, sync::Arc};

pub trait Container {
type Applied<E>: Container<Elem = E>;
type Elem;
type Raw;

fn into_raw(slf: Self) -> Self::Raw;

/// Constructs a new container from a pointer obtained via `into_raw`.
///
/// # Safety
///
/// - `raw` must have been obtained via `into_raw` on either `Self` or
/// `Self::Applied`
/// - `raw` may have been passed through `transmute_raw`, but must not
/// have been otherwise modified
/// - `Self::Elem` must have the same size, alignment, and bit validity
/// as the element type of the container that produced this `raw` via
/// `into_raw`
/// - `Self::Elem` must have `UnsafeCell`s at the same byte ranges as
/// the element type of the container that produced this `raw` via
/// `into_raw`
unsafe fn from_raw(raw: Self::Raw) -> Self;

fn transmute_raw<T>(raw: <Self::Applied<T> as Container>::Raw) -> Self::Raw;

/// Transmutes this container from one element type to another.
///
/// # Safety
///
/// `T` and `Self::Elem` must have the same size, alignment, and bit
/// validity. They must have `UnsafeCell`s at the same byte ranges.
unsafe fn transmute_from<T>(c: Self::Applied<T>) -> Self
where
Self: Sized,
{
let raw = <Self::Applied<T> as Container>::into_raw(c);
let raw = Self::transmute_raw::<T>(raw);

// SAFETY:
// - `raw` was obtained via `into_raw`
// - `raw` was passed through `transmute_raw`, but was not otherwise
// modified
// - The caller promises that `T` and `Self::Elem` have the same
// size, alignment, and bit validity
// - The caller promises that `T` and `Self::Elem` have
// `UnsafeCell`s at the same byte ranges
unsafe { Self::from_raw(raw) }
}
}

impl<T> Container for Box<T> {
type Applied<U> = Box<U>;
type Elem = T;
type Raw = *mut T;

fn into_raw(b: Box<T>) -> *mut T {
Box::into_raw(b)
}

unsafe fn from_raw(ptr: *mut T) -> Box<T> {
unsafe { Box::from_raw(ptr) }
}

fn transmute_raw<U>(raw: *mut U) -> *mut T {
raw.cast()
}
}

impl<T> Container for Rc<T> {
type Applied<U> = Rc<U>;
type Elem = T;
type Raw = *const T;

fn into_raw(r: Rc<T>) -> *const T {
Rc::into_raw(r)
}

unsafe fn from_raw(ptr: *const T) -> Rc<T> {
// SAFETY:
// - The caller promises that `ptr` was obtained via `into_raw` on
// `Self` or on `Self::Applied` (which is also `Rc`). Since all
// such types implement `into_raw` as `Rc::into_raw`, `ptr` was
// obtained via `Rc::into_raw`. The caller further promises that
// `ptr` was not modified other than being passed through
// `transmute_raw` which, by the same argument, is always
// implemented as a pointer type cast which preserves referent
// bytes and provenance.
// - The caller promises that the original element type and
// `Self::Elem` have the same size, alignment, and bit validity.
// - The caller promises that the original element type and
// `Self::Elem` have `UnsafeCell`s at the same byte ranges.
unsafe { Rc::from_raw(ptr) }
}

fn transmute_raw<U>(raw: *const U) -> *const T {
raw.cast()
}
}

impl<T> Container for Arc<T> {
type Applied<U> = Arc<U>;
type Elem = T;
type Raw = *const T;

fn into_raw(a: Arc<T>) -> *const T {
Arc::into_raw(a)
}

unsafe fn from_raw(ptr: *const T) -> Arc<T> {
// SAFETY:
// - The caller promises that `ptr` was obtained via `into_raw` on
// `Self` or on `Self::Applied` (which is also `Arc`). Since all
// such types implement `into_raw` as `Arc::into_raw`, `ptr` was
// obtained via `Arc::into_raw`. The caller further promises that
// `ptr` was not modified other than being passed through
// `transmute_raw` which, by the same argument, is always
// implemented as a pointer type cast which preserves referent
// bytes and provenance.
// - The caller promises that the original element type and
// `Self::Elem` have the same size, alignment, and bit validity.
// - The caller promises that the original element type and
// `Self::Elem` have `UnsafeCell`s at the same byte ranges.
unsafe { Arc::from_raw(ptr) }
}

fn transmute_raw<U>(raw: *const U) -> *const T {
raw.cast()
}
}
}

#[cfg(all(zerocopy_gat, feature = "alloc"))]
pub use container::*;

// NOTE: We can't change this to a `pub use core as core_reexport` until [1] is
// fixed or we update to a semver-breaking version (as of this writing, 0.8.0)
// on the `main` branch.
Expand Down

0 comments on commit 27dcf55

Please sign in to comment.