From ca3cf29fe9f03867a5426afa58c9e968deccb61a Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Sat, 12 Aug 2023 00:19:23 -0700 Subject: [PATCH 1/2] [project] Initial commit Makes progress on #196 --- src/lib.rs | 6 + src/project.rs | 673 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 679 insertions(+) create mode 100644 src/project.rs diff --git a/src/lib.rs b/src/lib.rs index c462c15d7e..f0e78afe3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -162,6 +162,12 @@ pub mod byteorder; #[doc(hidden)] pub mod derive_util; +// TODO(#196): Once we figure out how to make the `project!` macro safe +// (specifically, by statically rejecting projection through `deref`), remove +// this `#[doc(hidden)]` and make `project` part of our public API. +#[doc(hidden)] +pub mod project; + #[cfg(feature = "byteorder")] pub use crate::byteorder::*; #[cfg(any(feature = "derive", test))] diff --git a/src/project.rs b/src/project.rs new file mode 100644 index 0000000000..2067ba056e --- /dev/null +++ b/src/project.rs @@ -0,0 +1,673 @@ +// Copyright 2023 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! Field projection inside of any container type. +//! +//! # How to use this module (for container users) +//! +//! Just call `project!(&a.b.c)`. It's that easy! +//! +//! Okay, maybe you want a bit more detail... +//! +//! ## The quick version +//! +//! Here's a quick, very dense explanation. If you want a friendlier +//! explanation, skip to the next section. +//! +//! Given a container type, `C`, an inner type, `I`, and a field in `I`, `f: F`, +//! if `C` implements the [`Projectable`] trait, then `project!` allows you +//! to project from a `&C` to a `&C` which points to the `f` field within +//! the original `C`. For `c: &C`, this is invoked as `project!(&c.f)`. +//! +//! ## The long version +//! +//! Let's say you're using a crate which provides a container type like the +//! following: +//! +//! ```rust +//! /// An unaligned `T`. +//! /// +//! /// `Unalign` has the same layout as `T`, except that its alignment +//! /// is always 1 regardless of `T`'s alignment. +//! #[repr(C, packed)] +//! pub struct Unalign{ +//! # t: T, +//! # } +//! ``` +//! +//! You're using it with a type from your crate: +//! +//! ```rust +//! #[repr(C)] +//! struct UdpHeader { +//! src_port: u16, +//! dst_port: u16, +//! length: u16, +//! checksum: u16, +//! } +//! ``` +//! +//! Let's say you're reading UDP packets off the network. `UdpHeader` contains +//! `u16`s, and so (on some platforms) it has an alignment of 2. But you don't +//! have any control of where your packets are stored in memory, so you can't +//! construct a `&UdpHeader` from the bytes you've received off the network. +//! Instead, you construct an `&Unalign`. +//! +//! That's all well and good, but what if you want to hand out a reference to a +//! packet's source port? It's a `u16` that might not be validly-aligned, so you +//! can't just create a `&u16` like you would be able to do if you just had a +//! `&UdpHeader`. What you'd really like to do is take your +//! `&Unalign` and take a reference to the `src_port`... and get back +//! an `&Unalign`. That's what `project!` lets you do: +//! +//! ```rust +//! # use zerocopy::project; +//! # struct UdpHeader { +//! # src_port: u16, +//! # dst_port: u16, +//! # length: u16, +//! # checksum: u16, +//! # } +//! # #[repr(C, packed)] +//! # struct Unalign{ +//! # t: T, +//! # } +//! # unsafe impl zerocopy::project::Projectable> for Unalign { +//! # type Inner = T; +//! # } +//! # fn read_udp_header() -> &'static Unalign { todo!() } +//! # fn __main() { +//! let packet = read_udp_header(); +//! // SAFETY: We don't perform projection through any references or other +//! // fields that implement `Deref` or `DerefMut`. +//! let src_port = unsafe { project!(&packet.src_port) }; +//! # } +//! ``` +//! +//! # How to use this module (for container authors) +//! +//! TODO(#196): Fill this section in before removing `#[doc(hidden)]` from this +//! module. + +/// A container which supports field projection of its contained type. +/// +/// # Example +/// +/// ```rust +/// # use zerocopy::project::Projectable; +/// #[repr(transparent)] +/// struct Wrapper(T); +/// +/// unsafe impl Projectable> for Wrapper { +/// type Inner = T; +/// } +/// ``` +/// +/// # Safety +/// +/// If `P: Projectable`, then the following must hold: +/// - Given `p: *const P` or `p: *mut P`, it is valid to perform `let i = p as +/// *const P::Inner` or `let i = p as *mut P::Inner`. The size of the +/// referents of `p` and `i` must be identical (e.g. as reported by +/// `size_of_val_raw`). +/// - If the following hold: +/// - `p: &P` or `p: &mut P`. +/// - Given an `i: P::Inner` of size `size_of_val(p)`, there exists an `F` at +/// byte range `f` within `i`. +/// +/// ...then it is sound to materialize a `&W` or `&mut W` which points to range +/// `f` within `p`. +/// +/// Note that this definition holds regardless of whether `P`, `P::Inner`, or +/// `F` are sized or unsized. +/// +// TODO(#196): Can we relax this safety requirement to support other types? +// E.g., what about `PhantomData`? If we can relax this requirement, it must be +// the case that, for DSTs, the prefix and tail-slice-element sizes must be the +// same (put another way, given a reference to the outer type with a certain +// number of tail elements, a reference to the inner type with the same number +// of tail elements will reference the same memory region). This is trivially +// true of the current safety requirement, but might need to be explicitly +// called out depending on how we relax that requirement. +pub unsafe trait Projectable { + /// The inner type. + type Inner: ?Sized; +} + +// TODO(#196): Once we expose this module in our public API (not +// `#[doc(hidden)]`), expose this for implementors to use? +macro_rules! unsafe_impl_projectable { + ($($c:ident)::* $(: ?$sized:ident)?) => { + unsafe impl Projectable> for $($c)::* { + type Inner = T; + } + }; +} + +safety_comment! { + /// SAFETY: + /// `MaybeUninit` and `Wrapping` are both documented to have the same + /// layout as `T`. + unsafe_impl_projectable!(core::mem::MaybeUninit); + unsafe_impl_projectable!(core::num::Wrapping); +} + +/// Performs field projection on `outer`, projecting into the field of type `F` +/// at the address provided by `inner_to_field`. +/// +/// # Safety +/// +/// `outer_to_inner(o)` must return a pointer, `i`, which has the same address +/// as `o`. It must be the case that `size_of_val(o) == size_of_val(i)`. +/// +/// `inner_to_field(i)` must return a pointer, `f`, with the following property: +/// If `i` points to a validly-initialized `P::Inner`, then `f` points to a +/// validly-initialized `F` which lives within the memory region addressed by +/// `i`. `inner_to_field` may NOT assume that `i` *does* actually point to a +/// validly-initialized `P::Inner`. `inner_to_field` may also NOT assume that +/// `i` is aligned. More specifically, `inner_to_field` may only assume that it +/// is sound to invoke `core::ptr::addr_of!(*i)`; it may not assume anything +/// that is not logically deducible from that assumption. +/// +/// TODO: Maybe this safety requirement is too weak. While the "lives in the +/// same memory region" constraint is meant to prevent dereferencing inside +/// `inner_to_field`, it would technically permit dereferencing a +/// self-referential pointer - after all, it's allowed to assume that `i` points +/// to a validly-initialized `P::Inner`. What we really want to say is that `i` +/// isn't necessarily initialized, but still has the correct field offsets, but +/// how do we articulate that? +/// +/// `field_to_wrapped_field(f)` must return a pointer, `w`, which has the same +/// address as `f`. It must be the case that `size_of_val(f) == size_of_val(w)`. +#[doc(hidden)] +#[inline(always)] +pub fn project( + _unsafe: unsafe_token::UnsafeToken, + outer: &P, + outer_to_inner: OuterToInner, + inner_to_field: InnerToField, + field_to_wrapped_field: FieldToWrappedField, +) -> &W +where + P: Projectable + ?Sized, + // TODO(#196), TODO(https://github.com/rust-lang/reference/pull/1387), + // TODO(https://github.com/rust-lang/rust/pull/114330): Remove this bound + // once we support unsized projection (see comment on `Unalign` for more + // details). + P::Inner: Sized, + F: ?Sized, + W: ?Sized, + OuterToInner: FnOnce(*const P) -> *const Unalign, + InnerToField: FnOnce(*const Unalign) -> *const F, + FieldToWrappedField: FnOnce(*const F) -> *const W, +{ + let outer: *const P = outer; + let inner = outer_to_inner(outer); + // SAFETY: We promise to only call `inner_to_field` with a value `inner` + // such that it is sound to call `addr_of!(*inner)`. Since this `inner` is + // derived from a vanilla reference, it is guaranteed to be non-zero. Since + // it is of type `*const Unalign<_>`, which is a `repr(packed)` type (and + // thus has alignment 1), it is trivially aligned. Those are the only two + // safety preconditions required in order to call `addr_of!(*inner)`. + let field = inner_to_field(inner); + let wrapped_field = field_to_wrapped_field(field); + // SAFETY: + // - Since `outer: *const P` is derived from the `outer` argument, which is + // a `&P`, `outer: *const P` points to a valid `P`. + // - Since `P: Projectable`, `P` has the same size and field offsets as + // `P::Inner`. + // - By safety precondition, `inner` points to the same memory region as + // `outer`. Based on the preceding premises, `inner` points to a valid + // `P::Inner`. + // - By safety precondition, since `inner_to_field` is invoked with a + // pointer to a valid `P::Inner`, it returns a pointer to a valid `F` + // which lives in the same memory region. Thus, `field` points to a valid + // `F`. TODO: Is this actually sufficient to prove that `field` points to + // a valid `F`? See the TODO in the doc comment on this function. + // - Since `P: Projectable`, `F` has the same size and field offsets + // as `W`. + // - By safety precondition, `wrapped_field` points to the same memory + // region as `field`, which we know points to a valid `F`. Since `F` has + // the same size and field offsets as `W`, `wrapped_field` also points to + // a valid `W`. Thus, it is sound to dereference `wrapped_field`. + unsafe { &*wrapped_field } +} + +/// Performs field projection on `outer`, projecting into the field of type `F` +/// at the address provided by `inner_to_field`. +/// +/// # Safety +/// +/// `project_mut` has the same safety requirements as `project`. +#[doc(hidden)] +#[inline(always)] +pub fn project_mut( + _unsafe: unsafe_token::UnsafeToken, + outer: &mut P, + outer_to_inner: OuterToInner, + inner_to_field: InnerToField, + field_to_wrapped_field: FieldToWrappedField, +) -> &mut W +where + P: Projectable + ?Sized, + // TODO(#196), TODO(https://github.com/rust-lang/reference/pull/1387), + // TODO(https://github.com/rust-lang/rust/pull/114330): Remove this bound + // once we support unsized projection (see comment on `Unalign` for more + // details). + P::Inner: Sized, + F: ?Sized, + W: ?Sized, + OuterToInner: FnOnce(*mut P) -> *mut Unalign, + InnerToField: FnOnce(*mut Unalign) -> *mut F, + FieldToWrappedField: FnOnce(*mut F) -> *mut W, +{ + let outer: *mut P = outer; + let inner = outer_to_inner(outer); + let field = inner_to_field(inner); + let wrapped_field = field_to_wrapped_field(field); + // SAFETY: See the safety comment in `project`. The same arguments apply + // here. + unsafe { &mut *wrapped_field } +} + +/// Performs field projection. +/// +/// Given a wrapper, `w: W`, and a field type in `T`, `f: F`, +/// `project!(&w.f)` returns a reference to a `W` (this works for mutable +/// references too). +/// +/// # Safety +/// +/// It is unsound to project using a sequence of accesses that invoke +/// [`Deref::deref`] or [`DerefMut::deref_mut`]. +/// +/// [`Deref::deref`]: core::ops::Deref::deref +/// [`DerefMut::deref_mut`]: core::ops::DerefMut::deref_mut +// TODO(#196): Is there any way to teach Rust about when references are +// non-overlapping so you can borrow multiple fields mutably at a time? +#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`. +#[macro_export] +macro_rules! project { + // Note that it's very important that the `mut` branches comes first! If it + // came after the immutable branches, then a `mut` token could be matched by + // `$c:ident` in those branches. + (&mut $c:ident $($f:tt)*) => { + $crate::project!(&mut ($c) $($f)*) + }; + (&mut ($c:expr) $($f:tt)*) => { + $crate::project!( + @inner + BorrowMut, + borrow_mut, + project_mut, + *mut _, + inner, + &mut *inner, + addr_of_mut, + $c, + $($f)* + ) + }; + + (&$c:ident $($f:tt)*) => { + $crate::project!(&($c) $($f)*) + }; + (&($c:expr) $($f:tt)*) => { + $crate::project!( + @inner + Borrow, + borrow, + project, + *const _, + inner, + &*inner, + addr_of, + $c, + $($f)* + ) + }; + + ( + @inner + $borrow_trait:ident, + $borrow_method:ident, + $project_fn:ident, + $ptr_ty:ty, + $inner_name:ident, + $convert_inner_raw_to_ref:expr, + $addr_of:ident, + $c:expr, + $($f:tt)* + ) => {{ + // This function does nothing, but is unsafe to call, and so has the + // effect of requiring that the caller only invoke `project!` inside of + // an `unsafe` block. + $crate::project::promise_no_deref(); + // We generate an `UnsafeToken` so that `$project_fn` can itself be + // safe, and thus we don't need to put the entire call to `$project_fn` + // in an `unsafe` block. This, in turn, is done so that the + // meta-variables `$c` and `$($f)*` are not expanded inside of an + // `unsafe` block, which would allow safe Rust code to smuggle in unsafe + // code via a call to `project!` without needing to write the `unsafe` + // keyword. + // + // TODO: + // - Slicing seems to be bounds-checked at runtime if need be, but is + // this guaranteed by the reference/stdlib docs? + // + // SAFETY: + // - The arguments passed for `outer_to_inner` and + // `field_to_wrapped_field` are just `as` casts, so they return + // pointers to the same memory region as their arguments. + // - The argument passed for `inner_to_field` uses `addr_of!` (or + // `addr_of_mut!`) to take the address of a sequence of field + // accesses. By safety precondition of this macro, none of those field + // accesses can go through a dereference. The only other types of + // field accesses produce "places" which live entirely within the + // memory region of the argument to `inner_to_field` as required by + // the safety precondition of `project`/`project_mut`. + // + // Some older versions of Clippy have a bug in which they don't + // recognize the preceding safety comment. + #[allow(clippy::undocumented_unsafe_blocks)] + #[allow(unused_unsafe)] + let token = unsafe { $crate::project::unsafe_token::UnsafeToken::new() }; + use ::core::borrow::$borrow_trait as _; + $crate::project::$project_fn( + token, + // We call `borrow` or `borrow_mut` so that this macro works + // regardless of whether `$c` is owned or borrowed. + // + // TODO(#196): Is there any way to make sure this calls + // `Borrow::borrow` or `BorrowMut::borrow_mut` even if the type has + // an inherent method of the same name? + $c.$borrow_method(), + // Older versions of Clippy complain about this `as` cast; newer + // versions seem to know it's okay. + #[allow(clippy::as_conversions)] + |outer| outer as $ptr_ty, + |inner| if false { + // This branch is never executed, but allows us to ensure that + // `$($f)*` doesn't contain any unsafe code that isn't wrapped + // in an `unsafe` block. If it does, then wrapping it in + // `unsafe` - as we do in the `else` branch - would allow users + // to write unsafe code without needing to write `unsafe`. + // + // The way we accomplish this is to generate a reference from + // `inner` (which is a raw pointer). That allows us to extract + // the unsafe operation of converting to a reference and wrap it + // in an `unsafe` block on its own, while leaving the `$($f)*` + // not wrapped in an `unsafe` block. Note that this is NOT sound + // to execute in the general case, but that's okay because we're + // in an `if false` branch. For example, if the wrapper type is + // `#[repr(packed)]`, then `inner_ref` may not be validly + // aligned, which is unsound. + let $inner_name = inner; + // SAFETY: This code is never executed. + // + // Some older versions of Clippy have a bug in which they don't + // recognize the preceding safety comment. + #[allow(clippy::undocumented_unsafe_blocks)] + #[allow(unused_unsafe)] + let inner_ref = unsafe { $convert_inner_raw_to_ref }; + ::core::ptr::$addr_of!(inner_ref .0 $($f)*) + } else { + // SAFETY: By safety invariant of `project`/project_mut`, it is + // sound to call `addr_of!`/`addr_of_mut!` on `*inner`. By + // safety precondition of this macro, none of the field accesses + // go through a dereference, and so no loads are generated. + // Thus, even if `inner` does not point to a validly-initialized + // value, this call is still sound. + // + // Some older versions of Clippy have a bug in which they don't + // recognize the preceding safety comment. + #[allow(clippy::undocumented_unsafe_blocks)] + #[allow(unused_unsafe)] + unsafe { ::core::ptr::$addr_of!((*inner) .0 $($f)* ) } + }, + // Older versions of Clippy complain about this `as` cast; newer + // versions seem to know it's okay. + #[allow(clippy::as_conversions)] + |field| field as $ptr_ty, + ) + }}; +} + +#[doc(hidden)] +#[inline(always)] +pub const unsafe fn promise_no_deref() {} + +// TODO(#196), TODO(https://github.com/rust-lang/reference/pull/1387), +// TODO(https://github.com/rust-lang/rust/pull/114330): Remove this once it is +// no longer UB to use `addr_of!` with an unaligned pointer, and once Miri knows +// that this isn't UB. Note that this struct is the only reason that `project!` +// doesn't support unsized types, so removing this will also address that +// limitation. +#[allow(missing_debug_implementations)] +#[doc(hidden)] +#[repr(packed)] +pub struct Unalign(pub T); + +#[doc(hidden)] +pub mod unsafe_token { + /// A token used to prove that the `unsafe` keyword has been written + /// somewhere. + #[allow(missing_copy_implementations, missing_debug_implementations)] + pub struct UnsafeToken(()); + + impl UnsafeToken { + /// Constructs a new `UnsafeToken`. + /// + /// # Safety + /// + /// The caller is responsible for ensuring that they uphold the safety + /// invariants of any APIs which consume this token. + pub unsafe fn new() -> UnsafeToken { + UnsafeToken(()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Copy)] + #[repr(C, packed)] + struct Unalign(T); + + safety_comment! { + /// SAFETY: + /// `Unalign` has the same layout as `T`. + unsafe_impl_projectable!(Unalign); + } + + impl Clone for Unalign { + fn clone(&self) -> Unalign { + *self + } + } + + #[derive(Eq, PartialEq, Debug)] + #[repr(transparent)] + struct Wrapper(T); + safety_comment! { + /// SAFETY: + /// `Wrapper` has the same layout as `T`. + unsafe_impl_projectable!(Wrapper: ?Sized); + } + + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + struct Foo { + a: u8, + b: u16, + c: T, + } + + macro_rules! test_project { + (($c:ident $($f:tt)*) => $expect:expr) => {{ + // SAFETY: None of the tests in this module use fields which + // implement `Deref` or `DerefMut`. + // + // Some older versions of Clippy have a bug in which they don't + // recognize the preceding safety comment. + #[allow(clippy::undocumented_unsafe_blocks)] + #[allow(unused_unsafe)] + unsafe { + // Test with an immutable reference. + let f = project!(&$c $($f)*); + assert_eq!({ f.0 }, $expect); + // Test with a mutable reference. + let f = project!(&mut $c $($f)*); + assert_eq!({ f.0 }, $expect); + + // Run the same tests with `$c` in parentheses. + test_project!((($c) $($f)*) => $expect); + } + }}; + ((($c:expr) $($f:tt)*) => $expect:expr) => {{ + // SAFETY: None of the tests in this module use fields which + // implement `Deref` or `DerefMut`. + // + // Some older versions of Clippy have a bug in which they don't + // recognize the preceding safety comment. + #[allow(clippy::undocumented_unsafe_blocks)] + #[allow(unused_unsafe)] + unsafe { + // Test with an immutable reference. + let f = project!(&($c) $($f)*); + assert_eq!({ f.0 }, $expect); + // Test with a mutable reference. + let f = project!(&mut ($c) $($f)*); + assert_eq!({ f.0 }, $expect); + } + }}; + } + + #[test] + fn test_project() { + let mut u = Unalign(Foo:: { a: 1, b: 2, c: 3 }); + + test_project!((u.a) => 1); + test_project!((u.b) => 2); + test_project!((u.c) => 3); + } + + #[test] + fn test_project_complex() { + // Test projection using a complex expression rather than just the + // identifier of a local variable. + + let mut u = Unalign(Foo:: { a: 1, b: 2, c: 3 }); + + fn ident(t: T) -> T { + t + } + + // SAFETY: None of these projections go through a field which implements + // `Deref` or `DerefMut`. + unsafe { + let ua = project!(&(ident(&u)).a); + let ub = project!(&(ident(&u)).b); + assert_eq!({ ua.0 }, 1); + assert_eq!({ ub.0 }, 2); + + let uc = project!(&mut (ident(&mut u)).c); + assert_eq!({ uc.0 }, 3); + } + } + + #[test] + fn test_project_complex_access() { + let mut u = Unalign(Foo::> { a: 1, b: 2, c: Foo { a: 3, b: 4, c: 5 } }); + test_project!((u.c) => Foo { a: 3, b: 4, c: 5 }); + test_project!((u.c.a) => 3); + + let mut u = Unalign(Foo::<[u32; 3]> { a: 1, b: 2, c: [3, 4, 5] }); + test_project!((u.c) => [3, 4, 5]); + test_project!((u.c[0]) => 3); + + let mut u = Unalign([0u8, 1, 2]); + test_project!((u[0]) => 0); + test_project!((u[1]) => 1); + test_project!((u[2]) => 2); + + // Test that indexing works using variables rather than literals. + for i in 0u8..3 { + test_project!((u[usize::from(i)]) => i); + } + + let mut u = Unalign([[0u8, 1, 2], [3, 4, 5], [6, 7, 8]]); + test_project!((u[0][0]) => 0); + test_project!((u[1][1]) => 4); + test_project!((u[2][2]) => 8); + + // Test that indexing works using variables rather than literals. + for (i, elem) in [(0usize, 0u8), (1, 4), (2, 8)] { + test_project!((u[i][i]) => elem); + } + } + + // TODO(#196), TODO(https://github.com/rust-lang/reference/pull/1387), + // TODO(https://github.com/rust-lang/rust/pull/114330): Uncomment this once + // unsized projection is supported. + // + // #[test] + // fn test_project_unsized() { + // let inner = [0u8, 1, 2]; + // let inner_ref: &[u8] = &inner[..]; + // let wrapper_ref: &Wrapper<([u8],)> = unsafe { &*(inner_ref as *const _ as *const _) }; + + // let first = project!(&wrapper_ref.0[1]); + // assert_eq!(first, &Wrapper(1u8)); + // let first_two = project!(&wrapper_ref.0[0..2]); + // assert_eq!(&first_two.0, &[0, 1]); + // } + + #[test] + #[should_panic(expected = "index out of bounds: the len is 3 but the index is 3")] + fn test_project_out_of_bounds() { + let u = Unalign([0u8, 1, 2]); + + // SAFETY: None of these projections go through a field which implements + // `Deref` or `DerefMut`. + unsafe { + let u0 = project!(&u[0]); + assert_eq!({ u0.0 }, 0); + let _ = project!(&u[3]); + } + } + + #[test] + #[should_panic(expected = "index out of bounds: the len is 3 but the index is 3")] + fn test_project_out_of_bounds_variable() { + let u = Unalign([0u8, 1, 2]); + + // SAFETY: None of these projections go through a field which implements + // `Deref` or `DerefMut`. + unsafe { + let u0 = project!(&u[0]); + assert_eq!({ u0.0 }, 0); + let i = 3; + let _ = project!(&u[i]); + } + } + + #[test] + #[should_panic(expected = "range end index 4 out of range for slice of length 3")] + fn test_project_out_of_bounds_range() { + let u = Wrapper([0u8, 1, 2]); + + // SAFETY: None of these projections go through a field which implements + // `Deref` or `DerefMut`. + unsafe { + let u0 = project!(&u[0]); + assert_eq!({ u0.0 }, 0); + // Good catch, Clippy! But we do need to be able to test this, so + // shhh.... + #[allow(clippy::out_of_bounds_indexing)] + let _ = project!(&u[0..4]); + } + } +} From b042fa086554d6d2af1c1c3324ff7ec49a4051cf Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Fri, 11 Aug 2023 21:04:04 -0700 Subject: [PATCH 2/2] [WIP] TryFromBytes TODO: - Finish writing safety comments - Finish writing documentation - Add tests Makes progress on #5 --- src/derive_util.rs | 59 ++++ src/lib.rs | 678 +++++++++++++++++++++++++++++++++++++++++---- src/macros.rs | 73 ++++- 3 files changed, 744 insertions(+), 66 deletions(-) diff --git a/src/derive_util.rs b/src/derive_util.rs index 8f72ef09c6..593413a597 100644 --- a/src/derive_util.rs +++ b/src/derive_util.rs @@ -62,3 +62,62 @@ macro_rules! union_has_padding { false $(|| core::mem::size_of::<$t>() != core::mem::size_of::<$ts>())* }; } + +/// Implements `TryFromBytes` for a struct type by delegating to existing +/// implementations for each of its fields, and optionally supports a custom +/// validation method. +/// +/// ```rust +/// # use zerocopy::impl_try_from_bytes_for_struct; +/// +/// #[repr(C)] +/// struct Foo { +/// a: u8, +/// b: u16, +/// } +/// +/// impl_try_from_bytes_for_struct!(Foo { a: u8, b: u16 }); +/// +/// #[repr(transparent)] +/// struct Bar(Foo); +/// +/// impl Bar { +/// fn is_valid(&self) -> bool { +/// u16::from(self.0.a) < self.0.b +/// } +/// } +/// +/// impl_try_from_bytes_for_struct!(Bar { 0: Foo } => is_valid); +/// ``` +/// +/// # Safety +/// +/// `$ty` must be a struct type, `$f` must list every field's name, and `$f_ty` +/// must be the correct types for those fields. +#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`. +#[macro_export] +macro_rules! impl_try_from_bytes_for_struct { + ($ty:ty { $($f:tt: $f_ty:ty),* } $(=> $validation_method:ident)?) => { + // SAFETY: The caller promises that all fields are listed with their + // correct types. We validate that every field is valid, which is the + // only requirement for the entire struct to be valid. Thus, we + // correctly implement `is_bit_valid` as required by the trait's safety + // documentation. + #[allow(unused_qualifications)] + unsafe impl zerocopy::TryFromBytes for $ty { + fn is_bit_valid(bytes: &zerocopy::MaybeValid) -> bool { + true $(&& { + let f: &zerocopy::MaybeValid<$f_ty> = zerocopy::project!(&bytes.$f); + zerocopy::TryFromBytes::is_bit_valid(f) + })* + $(&& { + // SAFETY: We just validated that all of the struct's fields + // are valid, which means that the struct itself is valid. + // That is the only precondition of `assume_valid_ref`. + let slf = unsafe { bytes.assume_valid_ref() }; + slf.$validation_method() + })? + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index f0e78afe3e..814fbe1ff7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -188,6 +188,8 @@ use core::{ ptr, slice, }; +use project::Projectable; + #[cfg(feature = "alloc")] extern crate alloc; #[cfg(feature = "alloc")] @@ -399,7 +401,8 @@ pub unsafe trait FromZeroes { #[allow(clippy::as_conversions)] let max_alloc = (isize::MAX as usize).saturating_sub(align); assert!(size <= max_alloc); - // TODO(https://github.com/rust-lang/rust/issues/55724): Use `Layout::repeat` once it's stabilized. + // TODO(https://github.com/rust-lang/rust/issues/55724): Use + // `Layout::repeat` once it's stabilized. let layout = Layout::from_size_align(size, align).expect("total allocation size overflows `isize`"); @@ -572,6 +575,484 @@ pub unsafe trait FromBytes: FromZeroes { } } +/// Types which have a known [`MaybeUninit`] equivalent. +/// +/// Since `MaybeUninit` requires `T: Sized`, this trait exists to support +/// `MaybeUninit`-like behavior for unsized types. Roughly speaking, the +/// associated `MaybeUninit` type has the same layout and bit validity that +/// `MaybeUninit` would have if `MaybeUninit` supported both sized and +/// unsized types. +/// +/// # Safety +/// +/// The safety invariants on `MaybeUninit` must be upheld. +pub unsafe trait AsMaybeUninit { + /// A type which has the same layout as `Self`, but which has no validity + /// constraints. + /// + /// Roughly speaking, this type is equivalent to what [`MaybeUninit`] + /// would be if `MaybeUninit` supported unsized types. + /// + /// # Safety + /// + /// For `T: AsMaybeUninit`, the following must hold: + /// - Given `m: T::MaybeUninit`, it is sound to write uninitialized bytes at + /// every byte offset in `m` + /// - `T` and `T::MaybeUninit` have the same alignment requirement + /// - It is valid to use an `as` cast to convert a `t: *const T` to a `m: + /// *const T::MaybeUninit` and vice-versa (and likewise for `*mut T`/`*mut + /// T::MaybeUninit`). Regardless of which direction the conversion was + /// performed, the sizes of the pointers' referents are always equal (in + /// terms of an API which is not yet stable, `size_of_val_raw(t) == + /// size_of_val_raw(m)`). + /// - `T::MaybeUninit` contains [`UnsafeCell`]s at exactly the same byte + /// ranges that `T` does. + /// + /// [`MaybeUninit`]: core::mem::MaybeUninit + /// [`UnsafeCell`]: core::cell::UnsafeCell + type MaybeUninit: ?Sized; +} + +// SAFETY: See safety comment on `MaybeUninit`. +unsafe impl AsMaybeUninit for T { + // SAFETY: + // - `MaybeUninit` has no validity requirements, so it is sound to write + // uninitialized bytes at every offset. + // - `MaybeUninit` has the same layout as `T`, so they have the same + // alignment requirement. For the same reason, their sizes are equal. + // - Since their sizes are equal, raw pointers to both types are thin + // pointers, and thus can be converted using as casts. For the same + // reason, the sizes of these pointers' referents are always equal. + // - `MaybeUninit` has the same field offsets as `T`, and so it contains + // `UnsafeCell`s at exactly the same byte ranges as `T`. + type MaybeUninit = MaybeUninit; +} + +// SAFETY: See safety comment on `MaybeUninit`. +unsafe impl AsMaybeUninit for [T] { + // SAFETY: + // - `MaybeUninit` has no bit validity requirements and `[U]` has the same + // bit validity requirements as `U`, so `[MaybeUninit]` has no bit + // validity requirements. Thus, it is sound to write uninitialized bytes + // at every offset. + // - Since `MaybeUninit` has the same layout as `T`, and `[U]` has the + // same alignment as `U`, `[MaybeUninit]` has the same alignment as + // `[T]`. + // - `[T]` and `[MaybeUninit]` are both slice types, and so pointers can + // be converted using an `as` cast. Since `T` and `MaybeUninit` have + // the same size, and since such a cast preserves the number of elements + // in the slice, the referent slices themselves will have the same size. + // - `MaybeUninit` has the same field offsets as `[T]`, and so it + // contains `UnsafeCell`s at exactly the same byte ranges as `[T]`. + type MaybeUninit = [MaybeUninit]; +} + +// SAFETY: See safety comment on `MaybeUninit`. +unsafe impl AsMaybeUninit for str { + // SAFETY: `str` has the same layout as `[u8]`. Thus, the same safety + // argument for `<[u8] as AsMaybeUninit>::MaybeUninit` applies here. + type MaybeUninit = <[u8] as AsMaybeUninit>::MaybeUninit; +} + +/// A value which might or might not constitute a valid instance of `T`. +/// +/// `MaybeValid` has the same layout (size and alignment) and field offsets +/// as `T`. However, it may contain any bit pattern with a few restrictions: +/// Given `m: MaybeValid` and a byte offset, `b` in the range `[0, +/// size_of_val(m))`: +/// - If, in all valid instances `t: T`, the byte at offset `b` in `t` is +/// initialized, then the byte at offset `b` within `m` is guaranteed to be +/// initialized. +/// - Let `s` be the sequence of bytes of length `b` in the offset range `[0, +/// b)` in `m`. Let `TT` be the subset of valid instances of `T` which contain +/// this sequence in the offset range `[0, b)`. If, for all instances of `t: +/// T` in `TT`, the byte at offset `b` in `t` is initialized, then the byte at +/// offset `b` in `m` is guaranteed to be initialized. +/// +/// Pragmatically, this means that if `m` is guaranteed to contain an enum +/// type at a particular offset, and the enum discriminant stored in `m` +/// corresponds to a valid variant of that enum type, then it is guaranteed +/// that the appropriate bytes of `m` are initialized as defined by that +/// variant's layout (although note that the variant's layout may contain +/// another enum type, in which case the same rules apply depending on the +/// state of its discriminant, and so on recursively). +/// +/// # Safety +/// +/// Unsafe code may assume that an instance of `MaybeValid` satisfies the +/// constraints described above. Unsafe code may produce a `MaybeValid` or +/// modify the bytes of an existing `MaybeValid` so long as these constraints +/// are upheld. It is unsound to produce a `MaybeValid` which fails to uphold +/// these constraints. +#[repr(transparent)] +pub struct MaybeValid { + inner: T::MaybeUninit, +} + +safety_comment! { + /// SAFETY: + /// - `FromZeroes`, `FromBytes`: `MaybeValid` doesn't impose any bit + /// validity constraints beyond requiring that certain bytes are + /// initialized. `T: TryFromBytes` ensures that `T` doesn't contain any + /// `UnsafeCell`s, which in turn ensures that `T::MaybeUninit` (and thus + /// `MaybeValid`) doesn't contain any `UnsafeCell`s. Note that we + /// cannot require `T: FromZeroes` or `T: FromBytes` in the `FromZeroes` + /// and `FromBytes` impls. The reason is that we rely on `MaybeValid` + /// to be `FromBytes` in our default method impls on `TryFromBytes`, where + /// we don't have any guarantee that `T: FromZeroes` or `T: FromBytes`. + /// - `AsBytes`: `MaybeValid` requires that, if a byte in `T` is always + /// initialized, the equivalent byte in `MaybeValid` must be + /// initialized. `T: AsBytes` implies that all bytes in `T` must always be + /// initialized, and so all bytes in `MaybeValid` must always be + /// initialized, and so `MaybeValid` satisfies `AsBytes`. + /// - `Unaligned`: `MaybeValid` has the same alignment as `T`. + /// + /// TODO(#5): In a future in which we derive `AsMaybeUninit` rather than + /// using the blanket impl for all `T: Sized`, we can be more precise about + /// `UnsafeCell`s. In particular, instead of guaranteeing that + /// `T::MaybeUninit` contains `UnsafeCell`s where `T` does, but we can + /// guarantee that `T::MaybeUninit` never contains any `UnsafeCell`s + /// regardless of whether `T` does. We can do this by doing the following: + /// + /// ``` + /// unsafe impl AsMaybeUninnit for UnsafeCell { + /// type MaybeUninit = MaybeUninit; + /// } + /// ``` + /// + /// Then, in code emitted by custom derive, we can set `MaybeUninit` to an + /// anonymous struct type where each field type, `F`, is replaced by + /// `F::MaybeUninit`. Once we've done this, we can implement `TryFromBytes`, + /// `FromZeroes`, and `FromBytes` for `MaybeValid` with no bounds on `T`. + /// + /// TODO(#5): Implement `TryFromBytes` for `MaybeValid` once we support + /// unsized types or once we support implementing traits for `MaybeValid` + /// without bounds on `T` (see previous TODO). Currently, `T: TryFromBytes + /// => TryFromBytes for MaybeValid` doesn't work because the impl emitted + /// by `unsafe_impl!` isn't smart enough to realize that `MaybeValid` is + /// `Sized` (I suspect this is another instance of + /// https://github.com/rust-lang/rust/issues/115080, but I'm not sure). + unsafe_impl!(T: TryFromBytes => FromZeroes for MaybeValid); + unsafe_impl!(T: TryFromBytes => FromZeroes for MaybeValid<[T]>); + unsafe_impl!(T: TryFromBytes => FromBytes for MaybeValid); + unsafe_impl!(T: TryFromBytes => FromBytes for MaybeValid<[T]>); + unsafe_impl!(T: AsBytes => AsBytes for MaybeValid); + unsafe_impl!(T: AsBytes => AsBytes for MaybeValid<[T]>); + unsafe_impl!(T: Unaligned => Unaligned for MaybeValid); + unsafe_impl!(T: Unaligned => Unaligned for MaybeValid<[T]>); +} + +// SAFETY: See safety comment on `MaybeUninit`. +unsafe impl AsMaybeUninit for MaybeValid<[T]> { + // SAFETY: + // - `MaybeUninit` has no bit validity requirements and `[U]` has the same + // bit validity requirements as `U`, so `[MaybeUninit]` has no bit + // validity requirements. Thus, it is sound to write uninitialized bytes + // at every offset. + // - `MaybeValid` is `repr(transparent)`, and thus has the same layout + // and field offsets as its contained field of type `U::MaybeUninit`. In + // this case, `U = [T]`, and so `U::MaybeUninit = [MaybeUninit]`. Thus, + // `MaybeValid<[T]>` has the same layout and field offsets as + // `[MaybeUninit]`, which is what we set `MaybeUninit` to here. Thus, + // they trivially have the same alignment. + // - By the same token, their raw pointer types are trivially `as` castable + // and preserve size. + // - By the same token, `[MaybeUninit]` contains `UnsafeCell`s at the + // same byte ranges as `MaybeValid<[T]>` does. + type MaybeUninit = [MaybeUninit]; +} + +impl Default for MaybeValid { + fn default() -> MaybeValid { + MaybeValid { inner: MaybeUninit::uninit() } + } +} + +impl MaybeValid { + /// Converts this `MaybeValid` to a `T`. + /// + /// # Safety + /// + /// `self` must contain a valid `T`. + pub const unsafe fn assume_valid(self) -> T { + // SAFETY: The caller has promised that `self` contains a valid `T`. + // Since `T: Sized`, we know that `T::MaybeUninit = MaybeUninit`. + // Since `MaybeValid` is `repr(transparent)`, its layout is identical to + // the contained `MaybeUninit`. Thus, the promise that `self` contains a + // valid `T` implies that the contained `MaybeUninit` contains a valid + // `T`. + unsafe { self.inner.assume_init() } + } + + /// Converts this `&MaybeValid` to a `&T`. + /// + /// # Safety + /// + /// `self` must contain a valid `T`. + pub const unsafe fn assume_valid_ref(&self) -> &T { + // SAFETY: The caller has promised that `self` contains a valid `T`. + // Since `T: Sized`, we know that `T::MaybeUninit = MaybeUninit`. + // Since `MaybeValid` is `repr(transparent)`, its layout is identical to + // the contained `MaybeUninit`. Thus, the promise that `self` contains a + // valid `T` implies that the contained `MaybeUninit` contains a valid + // `T`. + // + // Further, thanks to the safety requirements on `T::MaybeUninit`, we + // know that there are `UnsafeCell`s at the same byte ranges in both + // types. See [1] for a discussion of why this is a required safety + // condition. + // + // [1] https://github.com/rust-lang/unsafe-code-guidelines/issues/455 + unsafe { self.inner.assume_init_ref() } + } + + /// Converts this `&mut MaybeValid` to a `&mut T`. + /// + /// # Safety + /// + /// `self` must contain a valid `T`. + pub unsafe fn assume_valid_mut(&mut self) -> &mut T { + // SAFETY: The caller has promised that `self` contains a valid `T`. + // Since `T: Sized`, we know that `T::MaybeUninit = MaybeUninit`. + // Since `MaybeValid` is `repr(transparent)`, its layout is identical to + // the contained `MaybeUninit`. Thus, the promise that `self` contains a + // valid `T` implies that the contained `MaybeUninit` contains a valid + // `T`. + // + // Further, thanks to the safety requirements on `T::MaybeUninit`, we + // know that there are `UnsafeCell`s at the same byte ranges in both + // types. See [1] for a discussion of why this is a required safety + // condition. + // + // [1] https://github.com/rust-lang/unsafe-code-guidelines/issues/455 + unsafe { self.inner.assume_init_mut() } + } +} + +impl MaybeValid<[T]> { + /// Converts a `MaybeValid<[T]>` to a `[MaybeValid]`. + /// + /// `MaybeValid` has the same layout as `T`, so these layouts are + /// equivalent. + pub const fn as_slice_of_maybe_valids(&self) -> &[MaybeValid] { + let inner: &[::MaybeUninit] = &self.inner; + let inner_ptr: *const [::MaybeUninit] = inner; + // Note: this Clippy warning is only emitted on our MSRV (1.61), but not + // on later versions of Clippy. Thus, we consider it spurious. + #[allow(clippy::as_conversions)] + let ret_ptr = inner_ptr as *const [MaybeValid]; + // SAFETY: Since `inner` is a `&[T::MaybeUninit]`, and `MaybeValid` + // is a `repr(transparent)` struct around `T::MaybeUninit`, `inner` has + // the same layout as `&[MaybeValid]`. + unsafe { &*ret_ptr } + } +} + +impl MaybeValid<[T; N]> { + /// Converts a `&MaybeValid<[T; N]>` to a `&MaybeValid<[T]>`. + // TODO(#64): Make this `const` once our MSRV is >= 1.64.0 (when + // `slice_from_raw_parts` was stabilized as `const`). + pub fn as_slice(&self) -> &MaybeValid<[T]> { + let base: *const MaybeValid<[T; N]> = self; + let slice_of_t: *const [T] = ptr::slice_from_raw_parts(base.cast::(), N); + // Note: this Clippy warning is only emitted on our MSRV (1.61), but not + // on later versions of Clippy. Thus, we consider it spurious. + #[allow(clippy::as_conversions)] + let mv_of_slice = slice_of_t as *const MaybeValid<[T]>; + // SAFETY: TODO + unsafe { &*mv_of_slice } + } +} + +// SAFETY: +// - Given `p: *const MaybeValid` or `p: *mut MaybeValid`, it is valid to +// perform `let i = p as *const T` or `let i = p as *mut T`: This is true +// because `MaybeValid` and `T` have the same sizedness. Further, if they +// are unsized, then they are both slice types. +// - The size of the referents of `p` and `i` must be identical: This is true +// because `MaybeValid` and `T` always have the same size, and if they +// are slice types, then their tail element types have the same sizes. +// - If the following hold: +// - `p: &MaybeValid` or `p: &mut MaybeValid` +// - Given `t: T` of size `size_of_val(p)`, there exists an `F` at byte range +// `f` within `i`. +// +// ... then it is sound to materialize a `&MaybeValid` or `&mut +// MaybeValid` which points to range `f` within `p`: TODO +unsafe impl Projectable> for MaybeValid { + type Inner = T; +} + +impl Debug for MaybeValid { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.pad(core::any::type_name::()) + } +} + +/// Types whose validity can be checked at runtime, allowing them to be +/// conditionally converted from byte slices. +/// +/// WARNING: Do not implement this trait yourself! Instead, use +/// `#[derive(TryFromBytes)]`. +/// +/// `TryFromBytes` types can safely be deserialized from an untrusted sequence +/// of bytes by performing a runtime check that the byte sequence contains a +/// valid instance of `Self`. +/// +/// `TryFromBytes` is ignorant of byte order. For byte order-aware types, see +/// the [`byteorder`] module. +/// +/// # Safety +/// +/// Unsafe code may assume that [`is_bit_valid`] is correct; that, if +/// `is_bit_valid(candidate)` returns true, `candidate` contains a valid `Self`; +/// and that it is sound to treat `candidate` as a `&Self`. +/// +/// Implementations of `TryFromBytes` must ensure that [`is_bit_valid`] +/// satisfies its documented safety invariants. +/// +/// It is unsound to implement `TryFromBytes` for a type which contains any +/// `UnsafeCell`s. +/// +/// [`is_bit_valid`]: TryFromBytes::is_bit_valid +// TODO(#5): Describe in the documentation that `TryFromBytes` is compositional? +// Describe anything about the `is_bit_valid` impls that the custom derive +// emits? +pub unsafe trait TryFromBytes: AsMaybeUninit { + /// Does this [`MaybeValid`] contain a valid instance of `Self`? + /// + /// # Safety + /// + /// Unsafe code may assume that, if `is_bit_valid(candidate)` returns true, + /// `candidate` contains a valid `Self`, and that it is sound to treat + /// `candidate` as a `&Self`. + fn is_bit_valid(candidate: &MaybeValid) -> bool; + + /// Attempts to interpret a byte slice as a `Self`. + /// + /// `try_from_ref` validates that `bytes` contains a valid `Self` as defined + /// by [`is_bit_valid`]. If it does, then `bytes` is reinterpreted as a + /// `Self`. + /// + /// [`is_bit_valid`]: TryFromBytes::is_bit_valid + // TODO(#251): In a future in which we distinguish between `FromBytes` and + // `RefFromBytes`, this requires `where Self: RefFromBytes` to disallow + // interior mutability. + fn try_from_ref(bytes: &[u8]) -> Option<&Self> + where + // TODO(#5): Support unsized types. + Self: Sized, + { + // TODO(https://github.com/rust-lang/rust/issues/115080): Inline this + // function once #115080 is resolved. + #[inline(always)] + fn try_read_from_inner(bytes: &[u8], is_bit_valid: F) -> Option<&T> + where + F: FnOnce(&MaybeValid) -> bool, + MaybeValid: FromBytes, + { + let maybe_valid = Ref::<_, MaybeValid>::new(bytes)?.into_ref(); + if is_bit_valid(maybe_valid) { + // SAFETY: `is_bit_valid` promises that it only returns true if + // its argument contains a valid `T`. This is exactly the safety + // precondition of `MaybeValid::assume_valid_ref`. + Some(unsafe { maybe_valid.assume_valid_ref() }) + } else { + None + } + } + + try_read_from_inner(bytes, Self::is_bit_valid) + } + + /// Attempts to interpret a mutable byte slice as a `Self`. + /// + /// `try_from_mut` validates that `bytes` contains a valid `Self` as defined + /// by [`is_bit_valid`]. If it does, then `bytes` is reinterpreted as a + /// `Self`. + /// + /// [`is_bit_valid`]: TryFromBytes::is_bit_valid + // TODO(#251): In a future in which we distinguish between `FromBytes` and + // `RefFromBytes`, this requires `where Self: RefFromBytes` to disallow + // interior mutability. + fn try_from_mut(bytes: &mut [u8]) -> Option<&mut Self> + where + // TODO(#5): Support unsized types. + Self: AsBytes + Sized, + { + // TODO(https://github.com/rust-lang/rust/issues/115080): Inline this + // function once #115080 is resolved. + #[inline(always)] + fn try_read_from_mut_inner(bytes: &mut [u8], is_bit_valid: F) -> Option<&mut T> + where + T: AsBytes, + F: FnOnce(&MaybeValid) -> bool, + MaybeValid: FromBytes, + { + let maybe_valid = Ref::<_, MaybeValid>::new(bytes)?.into_mut(); + if is_bit_valid(maybe_valid) { + // SAFETY: `is_bit_valid` promises that it only returns true if + // its argument contains a valid `T`. This is exactly the safety + // precondition of `MaybeValid::assume_valid_mut`. + Some(unsafe { maybe_valid.assume_valid_mut() }) + } else { + None + } + } + + try_read_from_mut_inner(bytes, Self::is_bit_valid) + } + + /// Attempts to read a `Self` from a byte slice. + /// + /// `try_read_from` validates that `bytes` contains a valid `Self` as + /// defined by [`is_bit_valid`]. If it does, then that `Self` is copied and + /// returned by-value. + /// + /// [`is_bit_valid`]: TryFromBytes::is_bit_valid + // TODO(#251): In a future in which we distinguish between `FromBytes` and + // `RefFromBytes`, this requires `where Self: RefFromBytes` to disallow + // interior mutability. + fn try_read_from(bytes: &[u8]) -> Option + where + Self: Sized, + { + // TODO(https://github.com/rust-lang/rust/issues/115080): Inline this + // function once #115080 is resolved. + #[inline(always)] + fn try_read_from_inner(bytes: &[u8], is_bit_valid: F) -> Option + where + F: FnOnce(&MaybeValid) -> bool, + MaybeValid: FromBytes, + { + // A note on performance: We unconditionally read `size_of::()` + // bytes into the local stack frame before validation. This has + // advantages and disadvantages: + // - It allows `MaybeValid` to be aligned to `T`, and thus allows + // `is_bit_valid` to operate on an aligned value. + // - It requires us to perform the copy even if validation fails. + // + // The authors believe that this is a worthwhile tradeoff. Allowing + // `is_bit_valid` to operate on an aligned value can make the + // generated machine code significantly smaller and faster. On the + // other hand, we expect the vast majority of calls to + // `try_read_from` to succeed, and in these cases, the copy will not + // be wasted. + let maybe_valid = MaybeValid::::read_from(bytes)?; + if is_bit_valid(&maybe_valid) { + // SAFETY: `is_bit_valid` promises that it only returns true if + // its argument contains a valid `T`. This is exactly the safety + // precondition of `MaybeValid::assume_valid`. + Some(unsafe { maybe_valid.assume_valid() }) + } else { + None + } + } + + try_read_from_inner(bytes, Self::is_bit_valid) + } +} + /// Types which are safe to treat as an immutable byte slice. /// /// WARNING: Do not implement this trait yourself! Instead, use @@ -688,8 +1169,9 @@ pub unsafe trait AsBytes { // could exist are other immutable references, and those don't allow // mutation. // - // TODO(#8): Update `AsRef` docs to require that `Self` doesn't allow - // interior mutability so that this bullet point is actually true. + // TODO(#8): Update `AsBytes` docs to require that `Self` doesn't + // allow interior mutability so that this bullet point is actually + // true. // - The total size of the resulting slice is no larger than // `isize::MAX` because no allocation produced by safe code can be // larger than `isize::MAX`. @@ -799,19 +1281,20 @@ safety_comment! { /// SAFETY: /// Per the reference [1], "the unit tuple (`()`) ... is guaranteed as a /// zero-sized type to have a size of 0 and an alignment of 1." - /// - `FromZeroes`, `FromBytes`: There is only one possible sequence of 0 - /// bytes, and `()` is inhabited. + /// - `TryFromBytes` (with no validator), `FromZeroes`, `FromBytes`: There + /// is only one possible sequence of 0 bytes, and `()` is inhabited. /// - `AsBytes`: Since `()` has size 0, it contains no padding bytes. /// - `Unaligned`: `()` has alignment 1. /// /// [1] https://doc.rust-lang.org/reference/type-layout.html#tuple-layout - unsafe_impl!((): FromZeroes, FromBytes, AsBytes, Unaligned); + unsafe_impl!((): TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); assert_unaligned!(()); } safety_comment! { /// SAFETY: - /// - `FromZeroes`, `FromBytes`: all bit patterns are valid for integers [1] + /// - `TryFromBytes` (with no validator), `FromZeroes`, `FromBytes`: all bit + /// patterns are valid for integers [1] /// - `AsBytes`: integers have no padding bytes [1] /// - `Unaligned` (`u8` and `i8` only): The reference [2] specifies the size /// of `u8` and `i8` as 1 byte. We also know that: @@ -823,26 +1306,26 @@ safety_comment! { /// [1] TODO(https://github.com/rust-lang/reference/issues/1291): Once the /// reference explicitly guarantees these properties, cite it. /// [2] https://doc.rust-lang.org/reference/type-layout.html#primitive-data-layout - unsafe_impl!(u8: FromZeroes, FromBytes, AsBytes, Unaligned); - unsafe_impl!(i8: FromZeroes, FromBytes, AsBytes, Unaligned); + unsafe_impl!(u8: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + unsafe_impl!(i8: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); assert_unaligned!(u8, i8); - unsafe_impl!(u16: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(i16: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(u32: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(i32: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(u64: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(i64: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(u128: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(i128: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(usize: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(isize: FromZeroes, FromBytes, AsBytes); + unsafe_impl!(u16: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(i16: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(u32: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(i32: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(u64: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(i64: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(u128: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(i128: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(usize: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(isize: TryFromBytes, FromZeroes, FromBytes, AsBytes); } safety_comment! { /// SAFETY: - /// - `FromZeroes`, `FromBytes`: the `{f32,f64}::from_bits` constructors' - /// documentation [1,2] states that they are currently equivalent to - /// `transmute`. [3] + /// - `TryFromBytes` (with no validator), `FromZeroes`, `FromBytes`: the + /// `{f32,f64}::from_bits` constructors' documentation [1, 2] states that + /// they are currently equivalent to `transmute`. [3] /// - `AsBytes`: the `{f32,f64}::to_bits` methods' documentation [4,5] /// states that they are currently equivalent to `transmute`. [3] /// @@ -854,8 +1337,8 @@ safety_comment! { /// reference explicitly guarantees these properties, cite it. /// [4] https://doc.rust-lang.org/nightly/std/primitive.f32.html#method.to_bits /// [5] https://doc.rust-lang.org/nightly/std/primitive.f64.html#method.to_bits - unsafe_impl!(f32: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(f64: FromZeroes, FromBytes, AsBytes); + unsafe_impl!(f32: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(f64: TryFromBytes, FromZeroes, FromBytes, AsBytes); } safety_comment! { @@ -871,6 +1354,17 @@ safety_comment! { /// [1] https://doc.rust-lang.org/reference/types/boolean.html unsafe_impl!(bool: FromZeroes, AsBytes, Unaligned); assert_unaligned!(bool); + /// SAFETY: + /// - Since `bool`'s single byte is always initialized, `MaybeValid`'s + /// single byte must also always be initialized. Thus, it is sound to + /// transmute a `MaybeValid` to a `u8`. Since `u8` has alignment 1, + /// there can never be any alignment issues, and so it is sound to + /// transmute a `&MaybeValid` to a `&u8`. + /// - All values less than 2 are valid instances of `bool` [1], and so this + /// is a sound implementation of `TryFromBytes::is_bit_valid`. + /// + /// [1] https://doc.rust-lang.org/reference/types/boolean.html + unsafe_impl!(bool: TryFromBytes; |byte: &u8| *byte < 2); } safety_comment! { /// SAFETY: @@ -882,6 +1376,22 @@ safety_comment! { /// /// [1] https://doc.rust-lang.org/reference/types/textual.html unsafe_impl!(char: FromZeroes, AsBytes); + /// SAFETY: + /// - Since `char`'s 4 bytes are always initialized, `MaybeValid`'s + /// bytes must also always be initialized. Thus, it is sound to transmute + /// a `MaybeValid` to a `[u8; 4]`. Since `[u8; 4]` has alignment 1, + /// there can never be any alignment issues, and so it is sound to + /// transmute a `&MaybeValid` to a `&[u8; 4]`. + /// - Since we use `from_ne_bytes`, `c` has the same bits as the argument to + /// `is_bit_valid`. `char::from_u32` guarantees that it returns `None` if + /// its input is not a valid `char` [1], and so this is a sound + /// implementation of `TryFromBytes::is_bit_valid`. + /// + /// [1] https://doc.rust-lang.org/std/primitive.char.html#method.from_u32 + unsafe_impl!(char: TryFromBytes; |bytes: &[u8; 4]| { + let c = u32::from_ne_bytes(*bytes); + char::from_u32(c).is_some() + }); } safety_comment! { /// SAFETY: @@ -894,6 +1404,22 @@ safety_comment! { /// /// [1] https://doc.rust-lang.org/reference/type-layout.html#str-layout unsafe_impl!(str: FromZeroes, AsBytes, Unaligned); + /// SAFETY: + /// - Since `str`'s bytes are all always initialized, `MaybeValid`'s + /// bytes must also always be initialized. Thus, it is sound to transmute + /// `MaybeValid` to `[u8]`. Since `str` and `[u8]` have the same + /// layout, they have the same alignment, and so it is sound to transmute + /// `&MaybeValid` to `&u8`. + /// - `str`'s bit validity requirement is that it is valid UTF-8. [1] Thus, + /// if `from_utf8` can successfully convert `bytes` to a `str`, then the + /// `str` is valid [2], and so this is a sound implementation of + /// `TryFromBytes::is_bit_valid`. + /// + /// [1] https://doc.rust-lang.org/reference/types/textual.html + /// [2] https://doc.rust-lang.org/core/str/fn.from_utf8.html + unsafe_impl!(str: TryFromBytes; |bytes: &[u8]| { + core::str::from_utf8(bytes).is_ok() + }); } safety_comment! { @@ -931,12 +1457,34 @@ safety_comment! { unsafe_impl!(NonZeroI128: AsBytes); unsafe_impl!(NonZeroUsize: AsBytes); unsafe_impl!(NonZeroIsize: AsBytes); + + /// SAFETY: + /// - `NonZeroXxx` has the same layout as `Xxx`. Also, every byte of + /// `NonZeroXxx` is required to be initialized, so it is guaranteed that + /// every byte of `MaybeValid` must also be initialized. Thus, + /// it is sound to transmute a `&MaybeValid` to a `&Xxx`. + /// - `NonZeroXxx`'s only validity constraint is that it is non-zero, which + /// all of these closures ensure. Thus, these closures are sound + /// implementations of `TryFromBytes::is_bit_valid`. + unsafe_impl!(NonZeroU8: TryFromBytes; |n: &u8| *n != 0); + unsafe_impl!(NonZeroI8: TryFromBytes; |n: &i8| *n != 0); + unsafe_impl!(NonZeroU16: TryFromBytes; |n: &u16| *n != 0); + unsafe_impl!(NonZeroI16: TryFromBytes; |n: &i16| *n != 0); + unsafe_impl!(NonZeroU32: TryFromBytes; |n: &u32| *n != 0); + unsafe_impl!(NonZeroI32: TryFromBytes; |n: &i32| *n != 0); + unsafe_impl!(NonZeroU64: TryFromBytes; |n: &u64| *n != 0); + unsafe_impl!(NonZeroI64: TryFromBytes; |n: &i64| *n != 0); + unsafe_impl!(NonZeroU128: TryFromBytes; |n: &u128| *n != 0); + unsafe_impl!(NonZeroI128: TryFromBytes; |n: &i128| *n != 0); + unsafe_impl!(NonZeroUsize: TryFromBytes; |n: &usize| *n != 0); + unsafe_impl!(NonZeroIsize: TryFromBytes; |n: &isize| *n != 0); } safety_comment! { /// SAFETY: - /// - `FromZeroes`, `FromBytes`, `AsBytes`: The Rust compiler reuses `0` - /// value to represent `None`, so `size_of::>() == - /// size_of::()`; see `NonZeroXxx` documentation. + /// - `TryFromBytes` (with no validator), `FromZeroes`, `FromBytes`, + /// `AsBytes`: The Rust compiler reuses `0` value to represent `None`, so + /// `size_of::>() == size_of::()`; see + /// `NonZeroXxx` documentation. /// - `Unaligned`: `NonZeroU8` and `NonZeroI8` document that /// `Option` and `Option` both have size 1. [1] [2] /// This is worded in a way that makes it unclear whether it's meant as a @@ -949,32 +1497,34 @@ safety_comment! { /// /// TODO(https://github.com/rust-lang/rust/pull/104082): Cite documentation /// for layout guarantees. - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes, Unaligned); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes, Unaligned); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); assert_unaligned!(Option, Option); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); } safety_comment! { /// SAFETY: /// For all `T`, `PhantomData` has size 0 and alignment 1. [1] - /// - `FromZeroes`, `FromBytes`: There is only one possible sequence of 0 - /// bytes, and `PhantomData` is inhabited. + /// - `TryFromBytes` (with no validator), `FromZeroes`, `FromBytes`: There + /// is only one possible sequence of 0 bytes, and `PhantomData` is + /// inhabited. /// - `AsBytes`: Since `PhantomData` has size 0, it contains no padding /// bytes. /// - `Unaligned`: Per the preceding reference, `PhantomData` has alignment /// 1. /// /// [1] https://doc.rust-lang.org/std/marker/struct.PhantomData.html#layout-1 + unsafe_impl!(T: ?Sized => TryFromBytes for PhantomData); unsafe_impl!(T: ?Sized => FromZeroes for PhantomData); unsafe_impl!(T: ?Sized => FromBytes for PhantomData); unsafe_impl!(T: ?Sized => AsBytes for PhantomData); @@ -990,6 +1540,7 @@ safety_comment! { /// /// [1] https://doc.rust-lang.org/nightly/core/num/struct.Wrapping.html#layout-1 /// [2] https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent + // TODO(#5): Implement `TryFromBytes` for `Wrapping`. unsafe_impl!(T: FromZeroes => FromZeroes for Wrapping); unsafe_impl!(T: FromBytes => FromBytes for Wrapping); unsafe_impl!(T: AsBytes => AsBytes for Wrapping); @@ -1001,7 +1552,7 @@ safety_comment! { // since it may contain uninitialized bytes. // /// SAFETY: - /// - `FromZeroes`, `FromBytes`: `MaybeUninit` has no restrictions on its + /// - `TryFromBytes` (with no validator), `FromZeroes`, `FromBytes`: `MaybeUninit` has no restrictions on its /// contents. Unfortunately, in addition to bit validity, `FromZeroes` and /// `FromBytes` also require that implementers contain no `UnsafeCell`s. /// Thus, we require `T: FromZeroes` and `T: FromBytes` in order to ensure @@ -1017,6 +1568,7 @@ safety_comment! { /// `FromBytes` and `RefFromBytes`, or if we introduce a separate /// `NoCell`/`Freeze` trait, we can relax the trait bounds for `FromZeroes` /// and `FromBytes`. + unsafe_impl!(T: TryFromBytes => TryFromBytes for MaybeUninit); unsafe_impl!(T: FromZeroes => FromZeroes for MaybeUninit); unsafe_impl!(T: FromBytes => FromBytes for MaybeUninit); unsafe_impl!(T: Unaligned => Unaligned for MaybeUninit); @@ -1039,6 +1591,7 @@ safety_comment! { /// code can only ever access a `ManuallyDrop` with all initialized bytes. /// - `Unaligned`: `ManuallyDrop` has the same layout (and thus alignment) /// as `T`, and `T: Unaligned` guarantees that that alignment is 1. + // TODO(#5): Implement `TryFromBytes` for `ManuallyDrop`. unsafe_impl!(T: ?Sized + FromZeroes => FromZeroes for ManuallyDrop); unsafe_impl!(T: ?Sized + FromBytes => FromBytes for ManuallyDrop); unsafe_impl!(T: ?Sized + AsBytes => AsBytes for ManuallyDrop); @@ -1064,15 +1617,34 @@ safety_comment! { /// (respectively). Furthermore, since an array/slice has "the same /// alignment of `T`", `[T]` and `[T; N]` are `Unaligned` if `T` is. /// + /// Finally, because of this layout equivalence, an instance of `[T]` or + /// `[T; N]` is valid if each `T` is valid. Thus, it is sound to implement + /// `TryFromBytes::is_bit_valid` by calling `is_bit_valid` on each element. + /// /// Note that we don't `assert_unaligned!` for slice types because /// `assert_unaligned!` uses `align_of`, which only works for `Sized` types. /// /// [1] https://doc.rust-lang.org/reference/type-layout.html#array-layout + unsafe_impl!(T: TryFromBytes, const N: usize => TryFromBytes for [T; N]; |c: &MaybeValid<[T; N]>| { + <[T] as TryFromBytes>::is_bit_valid(c.as_slice()) + }); unsafe_impl!(T: FromZeroes, const N: usize => FromZeroes for [T; N]); unsafe_impl!(T: FromBytes, const N: usize => FromBytes for [T; N]); unsafe_impl!(T: AsBytes, const N: usize => AsBytes for [T; N]); unsafe_impl!(T: Unaligned, const N: usize => Unaligned for [T; N]); assert_unaligned!([(); 0], [(); 1], [u8; 0], [u8; 1]); + unsafe_impl!(T: TryFromBytes => TryFromBytes for [T]; |c: &MaybeValid<[T]>| { + // TODO(https://github.com/rust-lang/rust/issues/115080): Inline this + // function once #115080 is resolved. + #[inline(always)] + fn is_bit_valid) -> bool>( + c: &MaybeValid<[T]>, + is_bit_valid: F, + ) -> bool { + c.as_slice_of_maybe_valids().iter().all(is_bit_valid) + } + is_bit_valid(c, T::is_bit_valid) + }); unsafe_impl!(T: FromZeroes => FromZeroes for [T]); unsafe_impl!(T: FromBytes => FromBytes for [T]); unsafe_impl!(T: AsBytes => AsBytes for [T]); @@ -1124,8 +1696,8 @@ safety_comment! { // Given this background, we can observe that: // - The size and bit pattern requirements of a SIMD type are equivalent to the // equivalent array type. Thus, for any SIMD type whose primitive `T` is -// `FromZeroes`, `FromBytes`, or `AsBytes`, that SIMD type is also -// `FromZeroes`, `FromBytes`, or `AsBytes` respectively. +// `FromZeroes`, `FromBytes`, `TryFromBytes`, or `AsBytes`, that SIMD type is +// also `FromZeroes`, `FromBytes`, `TryFromBytes`, or `AsBytes` respectively. // - Since no upper bound is placed on the alignment, no SIMD type can be // guaranteed to be `Unaligned`. // @@ -1136,21 +1708,23 @@ safety_comment! { // // See issue #38 [2]. While this behavior is not technically guaranteed, the // likelihood that the behavior will change such that SIMD types are no longer -// `FromZeroes`, `FromBytes`, or `AsBytes` is next to zero, as that would defeat -// the entire purpose of SIMD types. Nonetheless, we put this behavior behind -// the `simd` Cargo feature, which requires consumers to opt into this stability -// hazard. +// `FromZeroes`, `FromBytes`, `TryFromBytes`, or `AsBytes` is next to zero, as +// that would defeat the entire purpose of SIMD types. Nonetheless, we put this +// behavior behind the `simd` Cargo feature, which requires consumers to opt +// into this stability hazard. // // [1] https://rust-lang.github.io/unsafe-code-guidelines/layout/packed-simd-vectors.html // [2] https://github.com/rust-lang/unsafe-code-guidelines/issues/38 #[cfg(feature = "simd")] mod simd { - /// Defines a module which implements `FromZeroes`, `FromBytes`, and - /// `AsBytes` for a set of types from a module in `core::arch`. + /// Defines a module which implements `FromZeroes`, `FromBytes`, + /// `TryFromBytes`, and `AsBytes` for a set of types from a module in + /// `core::arch`. /// /// `$arch` is both the name of the defined module and the name of the /// module in `core::arch`, and `$typ` is the list of items from that module - /// to implement `FromZeroes`, `FromBytes`, and `AsBytes` for. + /// for which to implement `FromZeroes`, `FromBytes`, `TryFromBytes`, and + /// `AsBytes`. #[allow(unused_macros)] // `allow(unused_macros)` is needed because some // target/feature combinations don't emit any impls // and thus don't use this macro. @@ -1163,7 +1737,7 @@ mod simd { safety_comment! { /// SAFETY: /// See comment on module definition for justification. - $( unsafe_impl!($typ: FromZeroes, FromBytes, AsBytes); )* + $( unsafe_impl!($typ: TryFromBytes, FromZeroes, FromBytes, AsBytes); )* } } }; diff --git a/src/macros.rs b/src/macros.rs index 02ec611254..4345ade63e 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -13,51 +13,96 @@ /// /// Safety comment starts on its own line. /// macro_1!(args); /// macro_2! { args }; +/// /// SAFETY: +/// /// Subsequent safety comments are allowed but not required. +/// macro_3! { args }; /// } /// ``` /// /// The macro invocations are emitted, each decorated with the following /// attribute: `#[allow(clippy::undocumented_unsafe_blocks)]`. macro_rules! safety_comment { - (#[doc = r" SAFETY:"] $(#[doc = $_doc:literal])* $($macro:ident!$args:tt;)*) => { + (#[doc = r" SAFETY:"] $(#[doc = $_doc:literal])* $($macro:ident!$args:tt; $(#[doc = r" SAFETY:"] $(#[doc = $__doc:literal])*)?)*) => { #[allow(clippy::undocumented_unsafe_blocks)] const _: () = { $($macro!$args;)* }; } } /// Unsafely implements trait(s) for a type. +/// +/// # Safety +/// +/// The trait impl must be sound. +/// +/// When implementing `TryFromBytes`: +/// - If no `is_bit_valid` impl is provided, then it must be valid for +/// `is_bit_valid` to unconditionally return `true`. +/// - If an `is_bit_valid` impl is provided, then: +/// - It must be sound to transmute `&MaybeValid<$ty>` into `&$repr`. +/// - The impl of `is_bit_valid` must satisfy `TryFromByte`'s safety +/// requirements. macro_rules! unsafe_impl { // Implement `$trait` for `$ty` with no bounds. - ($ty:ty: $trait:ty) => { - unsafe impl $trait for $ty { fn only_derive_is_allowed_to_implement_this_trait() {} } + ($ty:ty: $trait:ident $(; |$candidate:ident: &$repr:ty| $is_bit_valid:expr)?) => { + unsafe impl $trait for $ty { + unsafe_impl!(@method $trait $(; |$candidate: &$repr| $is_bit_valid)?); + } }; // Implement all `$traits` for `$ty` with no bounds. - ($ty:ty: $($traits:ty),*) => { + ($ty:ty: $($traits:ident),*) => { $( unsafe_impl!($ty: $traits); )* }; // For all `$tyvar` with no bounds, implement `$trait` for `$ty`. - ($tyvar:ident => $trait:ident for $ty:ty) => { - unsafe impl<$tyvar> $trait for $ty { fn only_derive_is_allowed_to_implement_this_trait() {} } + ($tyvar:ident => $trait:ident for $ty:ty $(; |$candidate:ident: &$repr:ty| $is_bit_valid:expr)?) => { + unsafe impl<$tyvar> $trait for $ty { + unsafe_impl!(@method $trait $(; |$candidate: &$repr| $is_bit_valid)?); + } }; // For all `$tyvar: ?Sized` with no bounds, implement `$trait` for `$ty`. - ($tyvar:ident: ?Sized => $trait:ident for $ty:ty) => { - unsafe impl<$tyvar: ?Sized> $trait for $ty { fn only_derive_is_allowed_to_implement_this_trait() {} } + ($tyvar:ident: ?Sized => $trait:ident for $ty:ty $(; |$candidate:ident: &$repr:ty| $is_bit_valid:expr)?) => { + unsafe impl<$tyvar: ?Sized> $trait for $ty { + unsafe_impl!(@method $trait $(; |$candidate: &$repr| $is_bit_valid)?); + } }; // For all `$tyvar: $bound`, implement `$trait` for `$ty`. - ($tyvar:ident: $bound:path => $trait:ident for $ty:ty) => { - unsafe impl<$tyvar: $bound> $trait for $ty { fn only_derive_is_allowed_to_implement_this_trait() {} } + ($tyvar:ident: $bound:path => $trait:ident for $ty:ty $(; |$candidate:ident: &$repr:ty| $is_bit_valid:expr)?) => { + unsafe impl<$tyvar: $bound> $trait for $ty { + unsafe_impl!(@method $trait $(; |$candidate: &$repr| $is_bit_valid)?); + } }; // For all `$tyvar: $bound + ?Sized`, implement `$trait` for `$ty`. - ($tyvar:ident: ?Sized + $bound:path => $trait:ident for $ty:ty) => { - unsafe impl<$tyvar: ?Sized + $bound> $trait for $ty { fn only_derive_is_allowed_to_implement_this_trait() {} } + ($tyvar:ident: ?Sized + $bound:path => $trait:ident for $ty:ty $(; |$candidate:ident: &$repr:ty| $is_bit_valid:expr)?) => { + unsafe impl<$tyvar: ?Sized + $bound> $trait for $ty { + unsafe_impl!(@method $trait $(; |$candidate: &$repr| $is_bit_valid)?); + } }; // For all `$tyvar: $bound` and for all `const $constvar: $constty`, // implement `$trait` for `$ty`. - ($tyvar:ident: $bound:path, const $constvar:ident: $constty:ty => $trait:ident for $ty:ty) => { + ($tyvar:ident: $bound:path, const $constvar:ident: $constty:ty => $trait:ident for $ty:ty $(; |$candidate:ident: &$repr:ty| $is_bit_valid:expr)?) => { unsafe impl<$tyvar: $bound, const $constvar: $constty> $trait for $ty { - fn only_derive_is_allowed_to_implement_this_trait() {} + unsafe_impl!(@method $trait $(; |$candidate: &$repr| $is_bit_valid)?); } }; + + (@method TryFromBytes ; |$candidate:ident: &$repr:ty| $is_bit_valid:expr) => { + fn is_bit_valid(candidate: &MaybeValid) -> bool { + // SAFETY: The macro caller has promised that it is sound to + // transmute `&MaybeValid` to `&$repr`. + // + // Note: this Clippy warning is only emitted on our MSRV (1.61), but + // not on later versions of Clippy. Thus, we consider it spurious. + #[allow(clippy::as_conversions)] + let $candidate = unsafe { &*(candidate as *const MaybeValid as *const $repr) }; + $is_bit_valid + } + }; + (@method TryFromBytes) => { fn is_bit_valid(_: &MaybeValid) -> bool { true } }; + (@method $trait:ident) => { + fn only_derive_is_allowed_to_implement_this_trait() {} + }; + (@method $trait:ident; |$_candidate:ident: &$_repr:ty| $_is_bit_valid:expr) => { + compile_error!("Can't provide `is_bit_valid` impl for trait other than `TryFromBytes`"); + }; } /// Implements trait(s) for a type or verifies the given implementation by