Skip to content
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

[derive] Support derive(TryFromBytes) for structs #665

Merged
merged 1 commit into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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