Skip to content

Commit

Permalink
[derive] Support derive(TryFromBytes) for structs (#665)
Browse files Browse the repository at this point in the history
Supersedes #370.

Makes progress on #5.

Co-authored-by: Joshua Liebow-Feeser <hello@joshlf.com>
  • Loading branch information
jswrenn and joshlf authored Dec 8, 2023
1 parent 4a57178 commit e4b1f55
Show file tree
Hide file tree
Showing 27 changed files with 1,520 additions and 343 deletions.
9 changes: 8 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ pub use crate::wrappers::*;

#[cfg(any(feature = "derive", test))]
#[cfg_attr(doc_cfg, doc(cfg(feature = "derive")))]
pub use zerocopy_derive::Unaligned;
pub use zerocopy_derive::{TryFromBytes, Unaligned};

// `pub use` separately here so that we can mark it `#[doc(hidden)]`.
//
Expand Down Expand Up @@ -1193,6 +1193,13 @@ pub unsafe trait NoCell {
// TODO(#5): Update `try_from_ref` doc link once it exists
#[doc(hidden)]
pub unsafe trait TryFromBytes {
// The `Self: Sized` bound makes it so that `TryFromBytes` is still object
// safe.
#[doc(hidden)]
fn only_derive_is_allowed_to_implement_this_trait()
where
Self: Sized;

/// Does a given memory range contain a valid instance of `Self`?
///
/// # Safety
Expand Down
12 changes: 11 additions & 1 deletion src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ macro_rules! unsafe_impl {
};

(@method TryFromBytes ; |$candidate:ident: &$repr:ty| $is_bit_valid:expr) => {
#[allow(clippy::missing_inline_in_public_items)]
fn only_derive_is_allowed_to_implement_this_trait() {}

#[inline]
unsafe fn is_bit_valid(candidate: Ptr<'_, Self>) -> bool {
// SAFETY:
Expand Down Expand Up @@ -160,6 +163,9 @@ macro_rules! unsafe_impl {
}
};
(@method TryFromBytes ; |$candidate:ident: Ptr<$repr:ty>| $is_bit_valid:expr) => {
#[allow(clippy::missing_inline_in_public_items)]
fn only_derive_is_allowed_to_implement_this_trait() {}

#[inline]
unsafe fn is_bit_valid(candidate: Ptr<'_, Self>) -> bool {
// SAFETY:
Expand All @@ -174,7 +180,11 @@ macro_rules! unsafe_impl {
$is_bit_valid
}
};
(@method TryFromBytes) => { #[inline(always)] unsafe fn is_bit_valid(_: Ptr<'_, Self>) -> bool { true } };
(@method TryFromBytes) => {
#[allow(clippy::missing_inline_in_public_items)]
fn only_derive_is_allowed_to_implement_this_trait() {}
#[inline(always)] unsafe fn is_bit_valid(_: Ptr<'_, Self>) -> bool { true }
};
(@method $trait:ident) => {
#[allow(clippy::missing_inline_in_public_items)]
fn only_derive_is_allowed_to_implement_this_trait() {}
Expand Down
70 changes: 69 additions & 1 deletion src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ pub(crate) mod ptr {
/// than or equal to the size of the object referenced by `self`.
/// - The alignment of `U` is less than or equal to the alignment of
/// `T`.
pub(crate) unsafe fn cast_unsized<U: 'a + ?Sized, F: FnOnce(*mut T) -> *mut U>(
#[doc(hidden)]
#[inline]
pub unsafe fn cast_unsized<U: 'a + ?Sized, F: FnOnce(*mut T) -> *mut U>(
self,
cast: F,
) -> Ptr<'a, U> {
Expand Down Expand Up @@ -139,6 +141,72 @@ pub(crate) mod ptr {
// - `U: 'a`
Ptr { ptr, _lifetime: PhantomData }
}

/// Projects a field from `self`.
///
/// # Safety
///
/// ## Preconditions
///
/// The caller promises that `projector` projects a well-aligned field
/// of its argument. Its argument will be `self` casted to a raw
/// pointer.
///
/// ## Postconditions
///
/// If the preconditions of this function are met, this function will
/// return a `Ptr` to the field projected from `self` by `projector`.
#[doc(hidden)]
#[inline]
pub unsafe fn project<U: 'a + ?Sized>(
self,
projector: impl FnOnce(*mut T) -> *mut U,
) -> Ptr<'a, U> {
// SAFETY: `projector` is provided with `self` casted to a raw
// pointer.
let field = projector(self.ptr.as_ptr());

// SAFETY: We promise that `projector` is provided with `self`
// casted to a raw pointer, and the caller promises that `projector`
// is a field projection of its argument. By invariant on `Ptr`,
// `self` is non-null, and by contract on `projector`, so too will
// its return value.
//
// Note that field projection cannot wrap around the address space
// to null.
//
// TODO(https://github.com/rust-lang/rust/pull/116675): Cite
// documentation that allocated objects do not wrap around the
// address space.
let field = unsafe { NonNull::new_unchecked(field) };

// SAFETY: The safety invariants of `Ptr` (see definition) are
// satisfied:
// 1. `field` is derived from a valid Rust allocation, because
// `self` is derived from a valid Rust allocation, by invariant
// on `Ptr`, and `projector` (by contract) is a field projection
// through `self`.
// 2. `field` has the same provenance as `self`, because it derived
// from `self` using a series of provenance-preserving
// operations.
// 3. `field` is entirely contained in the allocation of `self`,
// because it is derived by `projector`, which is (by contract) a
// field projection through `self`.
// 4. `field` addresses a byte range whose length fits in an
// `isize`, because it is a field projection through `self` and
// thus is entirely contained by `self`, which satisfies this
// invariant.
// 5. `field` addresses a byte range which does not wrap around the
// address space (see above).
// 6. `field` is validly-aligned for `U`, by contract on
// `projector`.
// 7. The allocation of `field` is guaranteed to live for at least
// `'a`, because `field` is entirely contained in `self`, which
// lives for at least `'a` by invariant on `Ptr`.
// 8. `U: 'a`, because `field` is an element within `T`, and `T: 'a`
// by invariant on `Ptr`.
Ptr { ptr: field, _lifetime: PhantomData }
}
}

impl<'a> Ptr<'a, [u8]> {
Expand Down
96 changes: 64 additions & 32 deletions tests/ui-msrv/invalid-impls/invalid-impls.stderr
Original file line number Diff line number Diff line change
@@ -1,33 +1,65 @@
error[E0277]: the trait bound `T: zerocopy::TryFromBytes` is not satisfied
--> tests/ui-msrv/invalid-impls/../../../src/macros.rs
|
| impl<$($tyvar $(: $(? $optbound +)* $($bound +)*)?),*> Subtrait for $ty {}
| ^^^^^^^^ the trait `zerocopy::TryFromBytes` is not implemented for `T`
|
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:26:1
|
26 | impl_or_verify!(T => TryFromBytes for Foo<T>);
| --------------------------------------------- in this macro invocation
|
note: required because of the requirements on the impl of `zerocopy::TryFromBytes` for `Foo<T>`
--> tests/ui-msrv/invalid-impls/invalid-impls.rs:22:10
|
22 | #[derive(TryFromBytes, FromZeros, FromBytes, AsBytes, Unaligned)]
| ^^^^^^^^^^^^
note: required by a bound in `_::Subtrait`
--> tests/ui-msrv/invalid-impls/../../../src/macros.rs
|
| trait Subtrait: $trait {}
| ^^^^^^ required by this bound in `_::Subtrait`
|
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:26:1
|
26 | impl_or_verify!(T => TryFromBytes for Foo<T>);
| --------------------------------------------- in this macro invocation
= note: this error originates in the macro `impl_or_verify` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider restricting type parameter `T`
|
26 | impl_or_verify!(T: zerocopy::TryFromBytes => TryFromBytes for Foo<T>);
| ++++++++++++++++++++++++

error[E0277]: the trait bound `T: FromZeroes` is not satisfied
--> tests/ui-msrv/invalid-impls/../../../src/macros.rs
|
| impl<$($tyvar $(: $(? $optbound +)* $($bound +)*)?),*> Subtrait for $ty {}
| ^^^^^^^^ the trait `FromZeroes` is not implemented for `T`
|
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:26:1
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:27:1
|
26 | impl_or_verify!(T => FromZeros for Foo<T>);
27 | impl_or_verify!(T => FromZeros for Foo<T>);
| ------------------------------------------ in this macro invocation
|
note: required because of the requirements on the impl of `FromZeroes` for `Foo<T>`
--> tests/ui-msrv/invalid-impls/invalid-impls.rs:22:10
--> tests/ui-msrv/invalid-impls/invalid-impls.rs:22:24
|
22 | #[derive(FromZeros, FromBytes, AsBytes, Unaligned)]
| ^^^^^^^^^
22 | #[derive(TryFromBytes, FromZeros, FromBytes, AsBytes, Unaligned)]
| ^^^^^^^^^
note: required by a bound in `_::Subtrait`
--> tests/ui-msrv/invalid-impls/../../../src/macros.rs
|
| trait Subtrait: $trait {}
| ^^^^^^ required by this bound in `_::Subtrait`
|
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:26:1
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:27:1
|
26 | impl_or_verify!(T => FromZeros for Foo<T>);
27 | impl_or_verify!(T => FromZeros for Foo<T>);
| ------------------------------------------ in this macro invocation
= note: this error originates in the macro `impl_or_verify` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider restricting type parameter `T`
|
26 | impl_or_verify!(T: zerocopy::FromZeros => FromZeros for Foo<T>);
27 | impl_or_verify!(T: zerocopy::FromZeros => FromZeros for Foo<T>);
| +++++++++++++++++++++

error[E0277]: the trait bound `T: zerocopy::FromBytes` is not satisfied
Expand All @@ -36,30 +68,30 @@ error[E0277]: the trait bound `T: zerocopy::FromBytes` is not satisfied
| impl<$($tyvar $(: $(? $optbound +)* $($bound +)*)?),*> Subtrait for $ty {}
| ^^^^^^^^ the trait `zerocopy::FromBytes` is not implemented for `T`
|
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:27:1
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:28:1
|
27 | impl_or_verify!(T => FromBytes for Foo<T>);
28 | impl_or_verify!(T => FromBytes for Foo<T>);
| ------------------------------------------ in this macro invocation
|
note: required because of the requirements on the impl of `zerocopy::FromBytes` for `Foo<T>`
--> tests/ui-msrv/invalid-impls/invalid-impls.rs:22:21
--> tests/ui-msrv/invalid-impls/invalid-impls.rs:22:35
|
22 | #[derive(FromZeros, FromBytes, AsBytes, Unaligned)]
| ^^^^^^^^^
22 | #[derive(TryFromBytes, FromZeros, FromBytes, AsBytes, Unaligned)]
| ^^^^^^^^^
note: required by a bound in `_::Subtrait`
--> tests/ui-msrv/invalid-impls/../../../src/macros.rs
|
| trait Subtrait: $trait {}
| ^^^^^^ required by this bound in `_::Subtrait`
|
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:27:1
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:28:1
|
27 | impl_or_verify!(T => FromBytes for Foo<T>);
28 | impl_or_verify!(T => FromBytes for Foo<T>);
| ------------------------------------------ in this macro invocation
= note: this error originates in the macro `impl_or_verify` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider restricting type parameter `T`
|
27 | impl_or_verify!(T: zerocopy::FromBytes => FromBytes for Foo<T>);
28 | impl_or_verify!(T: zerocopy::FromBytes => FromBytes for Foo<T>);
| +++++++++++++++++++++

error[E0277]: the trait bound `T: zerocopy::AsBytes` is not satisfied
Expand All @@ -68,30 +100,30 @@ error[E0277]: the trait bound `T: zerocopy::AsBytes` is not satisfied
| impl<$($tyvar $(: $(? $optbound +)* $($bound +)*)?),*> Subtrait for $ty {}
| ^^^^^^^^ the trait `zerocopy::AsBytes` is not implemented for `T`
|
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:28:1
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:29:1
|
28 | impl_or_verify!(T => AsBytes for Foo<T>);
29 | impl_or_verify!(T => AsBytes for Foo<T>);
| ---------------------------------------- in this macro invocation
|
note: required because of the requirements on the impl of `zerocopy::AsBytes` for `Foo<T>`
--> tests/ui-msrv/invalid-impls/invalid-impls.rs:22:32
--> tests/ui-msrv/invalid-impls/invalid-impls.rs:22:46
|
22 | #[derive(FromZeros, FromBytes, AsBytes, Unaligned)]
| ^^^^^^^
22 | #[derive(TryFromBytes, FromZeros, FromBytes, AsBytes, Unaligned)]
| ^^^^^^^
note: required by a bound in `_::Subtrait`
--> tests/ui-msrv/invalid-impls/../../../src/macros.rs
|
| trait Subtrait: $trait {}
| ^^^^^^ required by this bound in `_::Subtrait`
|
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:28:1
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:29:1
|
28 | impl_or_verify!(T => AsBytes for Foo<T>);
29 | impl_or_verify!(T => AsBytes for Foo<T>);
| ---------------------------------------- in this macro invocation
= note: this error originates in the macro `impl_or_verify` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider restricting type parameter `T`
|
28 | impl_or_verify!(T: zerocopy::AsBytes => AsBytes for Foo<T>);
29 | impl_or_verify!(T: zerocopy::AsBytes => AsBytes for Foo<T>);
| +++++++++++++++++++

error[E0277]: the trait bound `T: zerocopy::Unaligned` is not satisfied
Expand All @@ -100,28 +132,28 @@ error[E0277]: the trait bound `T: zerocopy::Unaligned` is not satisfied
| impl<$($tyvar $(: $(? $optbound +)* $($bound +)*)?),*> Subtrait for $ty {}
| ^^^^^^^^ the trait `zerocopy::Unaligned` is not implemented for `T`
|
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:29:1
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:30:1
|
29 | impl_or_verify!(T => Unaligned for Foo<T>);
30 | impl_or_verify!(T => Unaligned for Foo<T>);
| ------------------------------------------ in this macro invocation
|
note: required because of the requirements on the impl of `zerocopy::Unaligned` for `Foo<T>`
--> tests/ui-msrv/invalid-impls/invalid-impls.rs:22:41
--> tests/ui-msrv/invalid-impls/invalid-impls.rs:22:55
|
22 | #[derive(FromZeros, FromBytes, AsBytes, Unaligned)]
| ^^^^^^^^^
22 | #[derive(TryFromBytes, FromZeros, FromBytes, AsBytes, Unaligned)]
| ^^^^^^^^^
note: required by a bound in `_::Subtrait`
--> tests/ui-msrv/invalid-impls/../../../src/macros.rs
|
| trait Subtrait: $trait {}
| ^^^^^^ required by this bound in `_::Subtrait`
|
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:29:1
::: tests/ui-msrv/invalid-impls/invalid-impls.rs:30:1
|
29 | impl_or_verify!(T => Unaligned for Foo<T>);
30 | impl_or_verify!(T => Unaligned for Foo<T>);
| ------------------------------------------ in this macro invocation
= note: this error originates in the macro `impl_or_verify` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider restricting type parameter `T`
|
29 | impl_or_verify!(T: zerocopy::Unaligned => Unaligned for Foo<T>);
30 | impl_or_verify!(T: zerocopy::Unaligned => Unaligned for Foo<T>);
| +++++++++++++++++++++
3 changes: 2 additions & 1 deletion tests/ui-nightly/invalid-impls/invalid-impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ use zerocopy_derive::*;

fn main() {}

#[derive(FromZeros, FromBytes, AsBytes, Unaligned)]
#[derive(TryFromBytes, FromZeros, FromBytes, AsBytes, Unaligned)]
#[repr(transparent)]
struct Foo<T>(T);

impl_or_verify!(T => TryFromBytes for Foo<T>);
impl_or_verify!(T => FromZeros for Foo<T>);
impl_or_verify!(T => FromBytes for Foo<T>);
impl_or_verify!(T => AsBytes for Foo<T>);
Expand Down
Loading

0 comments on commit e4b1f55

Please sign in to comment.