[Feature] extend TransparentWrapper conversion functions (#58)

* update rustfmt.toml

replace `merge_imports = true` (deprecated) with `imports_granularity = "Crate"`

* run `cargo +nightly fmt`

* rewrite docs and rename `Wrapped` to `Inner`

rewriting some docs for conciseness

rename `Wrapped` to `Inner`, because it's hard to visually differentiate between
`Wrapper` and `Wrapped`

* impl missing `TransparentWrapper::wrap_{}` fns

Implement 3 new wrapping functions on `TransparentWrapper` providing new
conversions.

- `TransparentWrapper::wrap(s: Inner) -> Self`
- `TransparentWrapper::wrap_slice(s: &[Inner]) -> &[Self]`
- `TransparentWrapper::wrap_slice_mut(s: &mut [Inner]) -> &mut [Self]`

* impl `TransparentWrapper::unwrap_{}` fns

Implement counterparts to `TransparentWrapper::wrap_{}` functions
providing reverse conversions.

- `TransparentWrapper::unwrap(self) -> Inner`
- `TransparentWrapper::unwrap_ref(&self) -> &Inner`
- `TransparentWrapper::unwrap_mut(&mut self) -> &mut Inner`
- `TransparentWrapper::unwrap_slice(s: &[Self]) -> &[Inner]`
- `TransparentWrapper::unwrap_slice_mut(s: &mut [Self]) -> &mut [Inner]`

* add `TransparentWrapper` UB test

This test is only for MIRI to check all trait functions on
`TransparentWrapper` if they cause any UB.
The output of the functions is not checked.

* small `TransparentWrapper` doc adjustments

* change fn signature on `TransparentWrapper`

Methods on `TransparentWrapper` trait are now associated functions.
They now take `Self` instead of `self` as argument)

- `TransparentWrapper::unwrap(s: Self)`
- `TransparentWrapper::unwrap_ref(s: &Self)`
- `TransparentWrapper::unwrap_mut(s: &mut Self)`
This commit is contained in:
Luis Wirth 2021-03-29 07:11:13 +02:00 committed by GitHub
parent 08a4b8fbc2
commit 30a96066fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 288 additions and 78 deletions

View File

@ -12,5 +12,5 @@ use_small_heuristics = "Max"
# Unstable
format_code_in_doc_comments = true
merge_imports = true
imports_granularity = "Crate"
wrap_comments = true

View File

@ -85,8 +85,8 @@ pub unsafe trait Contiguous: Copy + 'static {
/// `#[repr(Int)]` or `#[repr(C)]` attribute, (if it does not, it is
/// *unsound* to implement `Contiguous`!).
///
/// - For `#[repr(Int)]`, use the listed `Int`. e.g. `#[repr(u8)]` should
/// use `type Int = u8`.
/// - For `#[repr(Int)]`, use the listed `Int`. e.g. `#[repr(u8)]` should use
/// `type Int = u8`.
///
/// - For `#[repr(C)]`, use whichever type the C compiler will use to
/// represent the given enum. This is usually `c_int` (from `std::os::raw`

View File

@ -25,7 +25,9 @@
/// # use bytemuck::offset_of;
/// // enums can't derive default, and for this example we don't pick one
/// enum MyExampleEnum {
/// A, B, C,
/// A,
/// B,
/// C,
/// }
///
/// // so now our struct here doesn't have Default
@ -65,11 +67,11 @@
/// [rust-lang/rust#27060]: https://github.com/rust-lang/rust/issues/27060
///
/// <p style="background:rgba(255,181,77,0.16);padding:0.75em;">
/// <strong>Warning:</strong> This is only true for versions of bytemuck > 1.4.0.
/// Previous versions of
/// <strong>Warning:</strong> This is only true for versions of bytemuck >
/// 1.4.0. Previous versions of
/// <code style="background:rgba(41,24,0,0.1);">bytemuck::offset_of!</code>
/// will only emit a warning when used on the field of a packed struct in safe code,
/// which can lead to unsoundness.
/// will only emit a warning when used on the field of a packed struct in safe
/// code, which can lead to unsoundness.
/// </p>
///
/// For example, the following will fail to compile:
@ -91,7 +93,8 @@
/// ```compile_fail
/// # #[repr(C, packed)] #[derive(Default)] struct Example { field: u32 }
/// // Still doesn't compile:
/// #[allow(safe_packed_borrows)] {
/// #[allow(safe_packed_borrows)]
/// {
/// let _offset = bytemuck::offset_of!(Example, field);
/// }
/// ```

View File

@ -1,38 +1,42 @@
use super::*;
/// A trait which indicates that a type is a `repr(transparent)` wrapper around
/// the `Wrapped` value.
/// A trait which indicates that a type is a `#[repr(transparent)]` wrapper
/// around the `Inner` value.
///
/// This allows safely creating references to `T` from those to the `Wrapped`
/// type, using the `wrap_ref` and `wrap_mut` functions.
/// This allows safely copy transmuting between the `Inner` type and the
/// `TransparentWrapper` type.
/// Functions like `wrap_{}` convert from the inner
/// type to the wrapper type and `unwrap_{}` functions do the inverse conversion
/// from the wrapper type to the inner type.
///
/// # Safety
///
/// The safety contract of `TransparentWrapper` is relatively simple:
///
/// For a given `Wrapper` which implements `TransparentWrapper<Wrapped>`:
/// For a given `Wrapper` which implements `TransparentWrapper<Inner>`:
///
/// 1. Wrapper must be a `#[repr(transparent)]` wrapper around `Wrapped`. This
/// either means that it must be a `#[repr(transparent)]` struct which
/// contains a either a field of type `Wrapped` (or a field of some other
/// transparent wrapper for `Wrapped`) as the only non-ZST field.
/// 1. `Wrapper` must be a wrapper around `Inner` with an identical data
/// representations. This either means that it must be a
/// `#[repr(transparent)]` struct which contains a either a field of type
/// `Inner` (or a field of some other transparent wrapper for `Inner`) as the
/// only non-ZST field.
///
/// 2. Any fields *other* than the `Wrapped` field must be trivially
/// 2. Any fields *other* than the `Inner` field must be trivially
/// constructable ZSTs, for example `PhantomData`, `PhantomPinned`, etc.
///
/// 3. The `Wrapper` may not impose additional alignment requirements over
/// `Wrapped`.
/// `Inner`.
/// - Note: this is currently guaranteed by `repr(transparent)`, but there
/// have been discussions of lifting it, so it's stated here explicitly.
///
/// 4. The `wrap_ref` and `wrap_mut` functions on `TransparentWrapper` may not
/// be overridden.
/// 4. All functions on
/// `TransparentWrapper` **may not** be overridden.
///
/// ## Caveats
///
/// If the wrapper imposes additional constraints upon the wrapped type which
/// If the wrapper imposes additional constraints upon the inner type which
/// are required for safety, it's responsible for ensuring those still hold --
/// this generally requires preventing access to instances of the wrapped type,
/// this generally requires preventing access to instances of the inner type,
/// as implementing `TransparentWrapper<U> for T` means anybody can call
/// `T::cast_ref(any_instance_of_u)`.
///
@ -55,13 +59,13 @@ use super::*;
///
/// // interpret a reference to &SomeStruct as a &MyWrapper
/// let thing = SomeStruct::default();
/// let wrapped_ref: &MyWrapper = MyWrapper::wrap_ref(&thing);
/// let inner_ref: &MyWrapper = MyWrapper::wrap_ref(&thing);
///
/// // Works with &mut too.
/// let mut mut_thing = SomeStruct::default();
/// let wrapped_mut: &mut MyWrapper = MyWrapper::wrap_mut(&mut mut_thing);
/// let inner_mut: &mut MyWrapper = MyWrapper::wrap_mut(&mut mut_thing);
///
/// # let _ = (wrapped_ref, wrapped_mut); // silence warnings
/// # let _ = (inner_ref, inner_mut); // silence warnings
/// ```
///
/// ## Use with dynamically sized types
@ -80,56 +84,166 @@ use super::*;
/// let mut buf = [1, 2, 3u8];
/// let sm = Slice::wrap_mut(&mut buf);
/// ```
pub unsafe trait TransparentWrapper<Wrapped: ?Sized> {
/// Convert a reference to a wrapped type into a reference to the wrapper.
///
/// This is a trait method so that you can write `MyType::wrap_ref(...)` in
/// your code. It is part of the safety contract for this trait that if you
/// implement `TransparentWrapper<_>` for your type you **must not** override
/// this method.
pub unsafe trait TransparentWrapper<Inner: ?Sized> {
/// Convert the inner type into the wrapper type.
#[inline]
fn wrap_ref(s: &Wrapped) -> &Self {
fn wrap(s: Inner) -> Self
where
Self: Sized,
Inner: Sized,
{
// SAFETY: The unsafe contract requires that `Self` and `Inner` have
// identical representations.
unsafe { transmute_copy(&s) }
}
/// Convert a reference to the inner type into a reference to the wrapper
/// type.
#[inline]
fn wrap_ref(s: &Inner) -> &Self {
unsafe {
assert!(size_of::<*const Wrapped>() == size_of::<*const Self>());
// Using a pointer cast doesn't work here because rustc can't tell that the
// vtables match (if we lifted the ?Sized restriction, this would go away),
// and transmute doesn't work for the usual reasons it doesn't work inside
// generic functions.
assert!(size_of::<*const Inner>() == size_of::<*const Self>());
// A pointer cast does't work here because rustc can't tell that
// the vtables match (because of the `?Sized` restriction relaxation).
// A `transmute` doesn't work because the sizes are unspecified.
//
// SAFETY: The unsafe contract requires that these have identical
// representations. Using this transmute_copy instead of transmute here is
// annoying, but is required as `Self` and `Wrapped` have unspecified
// sizes still.
let wrapped_ptr = s as *const Wrapped;
let wrapper_ptr: *const Self = transmute_copy(&wrapped_ptr);
// SAFETY: The unsafe contract requires that these two have
// identical representations.
let inner_ptr = s as *const Inner;
let wrapper_ptr: *const Self = transmute_copy(&inner_ptr);
&*wrapper_ptr
}
}
/// Convert a mut reference to a wrapped type into a mut reference to the
/// wrapper.
///
/// This is a trait method so that you can write `MyType::wrap_mut(...)` in
/// your code. It is part of the safety contract for this trait that if you implement
/// `TransparentWrapper<_>` for your type you **must not** override this method.
/// Convert a mutable reference to the inner type into a mutable reference to
/// the wrapper type.
#[inline]
fn wrap_mut(s: &mut Wrapped) -> &mut Self {
fn wrap_mut(s: &mut Inner) -> &mut Self {
unsafe {
assert!(size_of::<*mut Wrapped>() == size_of::<*mut Self>());
// Using a pointer cast doesn't work here because rustc can't tell that the
// vtables match (if we lifted the ?Sized restriction, this would go away),
// and transmute doesn't work for the usual reasons it doesn't work inside
// generic functions.
assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
// A pointer cast does't work here because rustc can't tell that
// the vtables match (because of the `?Sized` restriction relaxation).
// A `transmute` doesn't work because the sizes are unspecified.
//
// SAFETY: The unsafe contract requires that these have identical
// representations. Using this transmute_copy instead of transmute here is
// annoying, but is required as `Self` and `Wrapped` have unspecified
// sizes still.
let wrapped_ptr = s as *mut Wrapped;
let wrapper_ptr: *mut Self = transmute_copy(&wrapped_ptr);
// SAFETY: The unsafe contract requires that these two have
// identical representations.
let inner_ptr = s as *mut Inner;
let wrapper_ptr: *mut Self = transmute_copy(&inner_ptr);
&mut *wrapper_ptr
}
}
/// Convert a slice to the inner type into a slice to the wrapper type.
#[inline]
fn wrap_slice(s: &[Inner]) -> &[Self]
where
Self: Sized,
Inner: Sized,
{
unsafe {
assert!(size_of::<*const Inner>() == size_of::<*const Self>());
assert!(align_of::<*const Inner>() == align_of::<*const Self>());
// SAFETY: The unsafe contract requires that these two have
// identical representations (size and alignment).
core::slice::from_raw_parts(s.as_ptr() as *const Self, s.len())
}
}
/// Convert a mutable slice to the inner type into a mutable slice to the
/// wrapper type.
#[inline]
fn wrap_slice_mut(s: &mut [Inner]) -> &mut [Self]
where
Self: Sized,
Inner: Sized,
{
unsafe {
assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
assert!(align_of::<*mut Inner>() == align_of::<*mut Self>());
// SAFETY: The unsafe contract requires that these two have
// identical representations (size and alignment).
core::slice::from_raw_parts_mut(s.as_mut_ptr() as *mut Self, s.len())
}
}
/// Convert the wrapper type into the inner type.
#[inline]
fn unwrap(s: Self) -> Inner
where
Self: Sized,
Inner: Sized,
{
unsafe { transmute_copy(&s) }
}
/// Convert a reference to the wrapper type into a reference to the inner
/// type.
#[inline]
fn unwrap_ref(s: &Self) -> &Inner {
unsafe {
assert!(size_of::<*const Inner>() == size_of::<*const Self>());
// A pointer cast does't work here because rustc can't tell that
// the vtables match (because of the `?Sized` restriction relaxation).
// A `transmute` doesn't work because the sizes are unspecified.
//
// SAFETY: The unsafe contract requires that these two have
// identical representations.
let wrapper_ptr = s as *const Self;
let inner_ptr: *const Inner = transmute_copy(&wrapper_ptr);
&*inner_ptr
}
}
/// Convert a mutable reference to the wrapper type into a mutable reference
/// to the inner type.
#[inline]
fn unwrap_mut(s: &mut Self) -> &mut Inner {
unsafe {
assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
// A pointer cast does't work here because rustc can't tell that
// the vtables match (because of the `?Sized` restriction relaxation).
// A `transmute` doesn't work because the sizes are unspecified.
//
// SAFETY: The unsafe contract requires that these two have
// identical representations.
let wrapper_ptr = s as *mut Self;
let inner_ptr: *mut Inner = transmute_copy(&wrapper_ptr);
&mut *inner_ptr
}
}
/// Convert a slice to the wrapped type into a slice to the inner type.
#[inline]
fn unwrap_slice(s: &[Self]) -> &[Inner]
where
Self: Sized,
Inner: Sized,
{
unsafe {
assert!(size_of::<*const Inner>() == size_of::<*const Self>());
assert!(align_of::<*const Inner>() == align_of::<*const Self>());
// SAFETY: The unsafe contract requires that these two have
// identical representations (size and alignment).
core::slice::from_raw_parts(s.as_ptr() as *const Inner, s.len())
}
}
/// Convert a mutable slice to the wrapped type into a mutable slice to the
/// inner type.
#[inline]
fn unwrap_slice_mut(s: &mut [Self]) -> &mut [Inner]
where
Self: Sized,
Inner: Sized,
{
unsafe {
assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
assert!(align_of::<*mut Inner>() == align_of::<*mut Self>());
// SAFETY: The unsafe contract requires that these two have
// identical representations (size and alignment).
core::slice::from_raw_parts_mut(s.as_mut_ptr() as *mut Inner, s.len())
}
}
}
unsafe impl<T> TransparentWrapper<T> for core::num::Wrapping<T> {}

View File

@ -25,7 +25,8 @@ fn test_try_cast_slice() {
Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
);
// by taking one byte off the end, we're aligned but would have slop bytes for u32
// by taking one byte off the end, we're aligned but would have slop bytes for
// u32
let the_bytes_len_minus1 = the_bytes.len() - 1;
let slop_bytes = &the_bytes[..the_bytes_len_minus1];
assert_eq!(
@ -62,7 +63,8 @@ fn test_try_cast_slice_mut() {
Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
);
// by taking one byte off the end, we're aligned but would have slop bytes for u32
// by taking one byte off the end, we're aligned but would have slop bytes for
// u32
let the_bytes_len_minus1 = the_bytes.len() - 1;
let slop_bytes = &mut the_bytes[..the_bytes_len_minus1];
assert_eq!(
@ -92,7 +94,10 @@ fn test_types() {
#[test]
fn test_bytes_of() {
assert_eq!(bytes_of(&0xaabbccdd_u32), &0xaabbccdd_u32.to_ne_bytes());
assert_eq!(bytes_of_mut(&mut 0xaabbccdd_u32), &mut 0xaabbccdd_u32.to_ne_bytes());
assert_eq!(
bytes_of_mut(&mut 0xaabbccdd_u32),
&mut 0xaabbccdd_u32.to_ne_bytes()
);
let mut a = 0xaabbccdd_u32;
let a_addr = &a as *const _ as usize;
// ensure addresses match.
@ -105,9 +110,18 @@ fn test_try_from_bytes() {
let u32s = [0xaabbccdd, 0x11223344_u32];
let bytes = bytemuck::cast_slice::<u32, u8>(&u32s);
assert_eq!(try_from_bytes::<u32>(&bytes[..4]), Ok(&u32s[0]));
assert_eq!(try_from_bytes::<u32>(&bytes[..5]), Err(PodCastError::SizeMismatch));
assert_eq!(try_from_bytes::<u32>(&bytes[..3]), Err(PodCastError::SizeMismatch));
assert_eq!(try_from_bytes::<u32>(&bytes[1..5]), Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned));
assert_eq!(
try_from_bytes::<u32>(&bytes[..5]),
Err(PodCastError::SizeMismatch)
);
assert_eq!(
try_from_bytes::<u32>(&bytes[..3]),
Err(PodCastError::SizeMismatch)
);
assert_eq!(
try_from_bytes::<u32>(&bytes[1..5]),
Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
);
}
#[test]
@ -117,9 +131,18 @@ fn test_try_from_bytes_mut() {
let bytes = bytemuck::cast_slice_mut::<u32, u8>(&mut u32s);
assert_eq!(try_from_bytes_mut::<u32>(&mut bytes[..4]), Ok(&mut abcd));
assert_eq!(try_from_bytes_mut::<u32>(&mut bytes[..4]), Ok(&mut abcd));
assert_eq!(try_from_bytes_mut::<u32>(&mut bytes[..5]), Err(PodCastError::SizeMismatch));
assert_eq!(try_from_bytes_mut::<u32>(&mut bytes[..3]), Err(PodCastError::SizeMismatch));
assert_eq!(try_from_bytes::<u32>(&mut bytes[1..5]), Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned));
assert_eq!(
try_from_bytes_mut::<u32>(&mut bytes[..5]),
Err(PodCastError::SizeMismatch)
);
assert_eq!(
try_from_bytes_mut::<u32>(&mut bytes[..3]),
Err(PodCastError::SizeMismatch)
);
assert_eq!(
try_from_bytes::<u32>(&mut bytes[1..5]),
Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
);
}
#[test]
@ -136,7 +159,10 @@ fn test_from_bytes_mut() {
let a_addr = &a as *const _ as usize;
let aligned_bytes = bytemuck::bytes_of_mut(&mut a);
assert_eq!(*from_bytes_mut::<u32>(aligned_bytes), 0xaabbccdd_u32);
assert_eq!(from_bytes_mut::<u32>(aligned_bytes) as *const u32 as usize, a_addr);
assert_eq!(
from_bytes_mut::<u32>(aligned_bytes) as *const u32 as usize,
a_addr
);
}
// like #[should_panic], but can be a part of another test, instead of requiring
@ -144,7 +170,10 @@ fn test_from_bytes_mut() {
macro_rules! should_panic {
($ex:expr) => {
assert!(
std::panic::catch_unwind(|| { let _ = $ex; }).is_err(),
std::panic::catch_unwind(|| {
let _ = $ex;
})
.is_err(),
concat!("should have panicked: `", stringify!($ex), "`")
);
};

View File

@ -1,7 +1,7 @@
#![cfg(feature = "derive")]
#![allow(dead_code)]
use bytemuck::{Zeroable, Pod, TransparentWrapper};
use bytemuck::{Pod, TransparentWrapper, Zeroable};
#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)]
@ -21,5 +21,5 @@ struct TransparentSingle {
#[transparent(u16)]
struct TransparentWithZeroSized {
a: u16,
b: ()
}
b: (),
}

64
tests/transparent.rs Normal file
View File

@ -0,0 +1,64 @@
// Currently this test doesn't actually check the output of the functions.
// It's only here for miri to check for any potential undefined behaviour.
// TODO: check function results
#[test]
fn test_transparent_wrapper() {
// An external type defined in a different crate.
#[derive(Copy, Clone, Default)]
struct Foreign(u8);
use bytemuck::TransparentWrapper;
#[derive(Copy, Clone)]
#[repr(transparent)]
struct Wrapper(Foreign);
unsafe impl TransparentWrapper<Foreign> for Wrapper {}
// Traits can be implemented on crate-local wrapper.
unsafe impl bytemuck::Zeroable for Wrapper {}
unsafe impl bytemuck::Pod for Wrapper {}
let _: u8 = bytemuck::cast(Wrapper::wrap(Foreign::default()));
let _: Foreign = Wrapper::unwrap(bytemuck::cast(u8::default()));
let _: &u8 = bytemuck::cast_ref(Wrapper::wrap_ref(&Foreign::default()));
let _: &Foreign = Wrapper::unwrap_ref(bytemuck::cast_ref(&u8::default()));
let _: &mut u8 =
bytemuck::cast_mut(Wrapper::wrap_mut(&mut Foreign::default()));
let _: &mut Foreign =
Wrapper::unwrap_mut(bytemuck::cast_mut(&mut u8::default()));
let _: &[u8] =
bytemuck::cast_slice(Wrapper::wrap_slice(&[Foreign::default()]));
let _: &[Foreign] =
Wrapper::unwrap_slice(bytemuck::cast_slice(&[u8::default()]));
let _: &mut [u8] =
bytemuck::cast_slice_mut(Wrapper::wrap_slice_mut(
&mut [Foreign::default()],
));
let _: &mut [Foreign] =
Wrapper::unwrap_slice_mut(bytemuck::cast_slice_mut(&mut [u8::default()]));
let _: &[u8] = bytemuck::bytes_of(Wrapper::wrap_ref(&Foreign::default()));
let _: &Foreign = Wrapper::unwrap_ref(bytemuck::from_bytes(&[u8::default()]));
let _: &mut [u8] =
bytemuck::bytes_of_mut(Wrapper::wrap_mut(&mut Foreign::default()));
let _: &mut Foreign =
Wrapper::unwrap_mut(bytemuck::from_bytes_mut(&mut [u8::default()]));
// not sure if this is the right usage
let _ =
bytemuck::pod_align_to::<_, u8>(Wrapper::wrap_slice(&[Foreign::default()]));
// counterpart?
// not sure if this is the right usage
let _ = bytemuck::pod_align_to_mut::<_, u8>(Wrapper::wrap_slice_mut(&mut [
Foreign::default(),
]));
// counterpart?
}