blob: 6da4fcffce0be0b52624764e9a4fc856a20ea287 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors
//
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
// This file may not be copied, modified, or distributed except according to
// those terms.
//! Derive macros for [zerocopy]'s traits.
//!
//! [zerocopy]: https://docs.rs/zerocopy
// Sometimes we want to use lints which were added after our MSRV.
// `unknown_lints` is `warn` by default and we deny warnings in CI, so without
// this attribute, any unknown lint would cause a CI failure when testing with
// our MSRV.
#![allow(unknown_lints)]
#![deny(renamed_and_removed_lints)]
#![deny(clippy::all, clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks)]
#![deny(
rustdoc::bare_urls,
rustdoc::broken_intra_doc_links,
rustdoc::invalid_codeblock_attributes,
rustdoc::invalid_html_tags,
rustdoc::invalid_rust_codeblocks,
rustdoc::missing_crate_level_docs,
rustdoc::private_intra_doc_links
)]
#![recursion_limit = "128"]
mod r#enum;
mod ext;
#[cfg(test)]
mod output_tests;
mod repr;
use proc_macro2::{TokenStream, TokenTree};
use quote::ToTokens;
use {
proc_macro2::Span,
quote::quote,
syn::{
parse_quote, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, Expr, ExprLit,
ExprUnary, GenericParam, Ident, Lit, Path, Type, UnOp, WherePredicate,
},
};
use {crate::ext::*, crate::repr::*};
// TODO(https://github.com/rust-lang/rust/issues/54140): Some errors could be
// made better if we could add multiple lines of error output like this:
//
// error: unsupported representation
// --> enum.rs:28:8
// |
// 28 | #[repr(transparent)]
// |
// help: required by the derive of FromBytes
//
// Instead, we have more verbose error messages like "unsupported representation
// for deriving FromZeros, FromBytes, IntoBytes, or Unaligned on an enum"
//
// This will probably require Span::error
// (https://doc.rust-lang.org/nightly/proc_macro/struct.Span.html#method.error),
// which is currently unstable. Revisit this once it's stable.
/// Defines a derive function named `$outer` which parses its input
/// `TokenStream` as a `DeriveInput` and then invokes the `$inner` function.
///
/// Note that the separate `$outer` parameter is required - proc macro functions
/// are currently required to live at the crate root, and so the caller must
/// specify the name in order to avoid name collisions.
macro_rules! derive {
($trait:ident => $outer:ident => $inner:ident) => {
#[proc_macro_derive($trait)]
pub fn $outer(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(ts as DeriveInput);
$inner(&ast, Trait::$trait).into_ts().into()
}
};
}
trait IntoTokenStream {
fn into_ts(self) -> TokenStream;
}
impl IntoTokenStream for TokenStream {
fn into_ts(self) -> TokenStream {
self
}
}
impl IntoTokenStream for Result<TokenStream, Error> {
fn into_ts(self) -> TokenStream {
match self {
Ok(ts) => ts,
Err(err) => err.to_compile_error(),
}
}
}
derive!(KnownLayout => derive_known_layout => derive_known_layout_inner);
derive!(Immutable => derive_no_cell => derive_no_cell_inner);
derive!(TryFromBytes => derive_try_from_bytes => derive_try_from_bytes_inner);
derive!(FromZeros => derive_from_zeros => derive_from_zeros_inner);
derive!(FromBytes => derive_from_bytes => derive_from_bytes_inner);
derive!(IntoBytes => derive_into_bytes => derive_into_bytes_inner);
derive!(Unaligned => derive_unaligned => derive_unaligned_inner);
derive!(ByteHash => derive_hash => derive_hash_inner);
derive!(ByteEq => derive_eq => derive_eq_inner);
/// Deprecated: prefer [`FromZeros`] instead.
#[deprecated(since = "0.8.0", note = "`FromZeroes` was renamed to `FromZeros`")]
#[doc(hidden)]
#[proc_macro_derive(FromZeroes)]
pub fn derive_from_zeroes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_from_zeros(ts)
}
/// Deprecated: prefer [`IntoBytes`] instead.
#[deprecated(since = "0.8.0", note = "`AsBytes` was renamed to `IntoBytes`")]
#[doc(hidden)]
#[proc_macro_derive(AsBytes)]
pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_into_bytes(ts)
}
fn derive_known_layout_inner(ast: &DeriveInput, _top_level: Trait) -> Result<TokenStream, Error> {
let is_repr_c_struct = match &ast.data {
Data::Struct(..) => {
let repr = StructUnionRepr::from_attrs(&ast.attrs)?;
if repr.is_c() {
Some(repr)
} else {
None
}
}
Data::Enum(..) | Data::Union(..) => None,
};
let fields = ast.data.fields();
let (self_bounds, inner_extras, outer_extras) = if let (
Some(repr),
Some((trailing_field, leading_fields)),
) = (is_repr_c_struct, fields.split_last())
{
let (_vis, trailing_field_name, trailing_field_ty) = trailing_field;
let leading_fields_tys = leading_fields.iter().map(|(_vis, _name, ty)| ty);
let core_path = quote!(::zerocopy::util::macro_util::core_reexport);
let repr_align = repr
.get_align()
.map(|align| {
let align = align.t.get();
quote!(#core_path::num::NonZeroUsize::new(#align as usize))
})
.unwrap_or_else(|| quote!(#core_path::option::Option::None));
let repr_packed = repr
.get_packed()
.map(|packed| {
let packed = packed.get();
quote!(#core_path::num::NonZeroUsize::new(#packed as usize))
})
.unwrap_or_else(|| quote!(#core_path::option::Option::None));
let make_methods = |trailing_field_ty| {
quote! {
// SAFETY:
// - The returned pointer has the same address and provenance as
// `bytes`:
// - The recursive call to `raw_from_ptr_len` preserves both
// address and provenance.
// - The `as` cast preserves both address and provenance.
// - `NonNull::new_unchecked` preserves both address and
// provenance.
// - If `Self` is a slice DST, the returned pointer encodes
// `elems` elements in the trailing slice:
// - This is true of the recursive call to `raw_from_ptr_len`.
// - `trailing.as_ptr() as *mut Self` preserves trailing slice
// element count [1].
// - `NonNull::new_unchecked` preserves trailing slice element
// count.
//
// [1] Per https://doc.rust-lang.org/reference/expressions/operator-expr.html#pointer-to-pointer-cast:
//
// `*const T`` / `*mut T` can be cast to `*const U` / `*mut U`
// with the following behavior:
// ...
// - If `T` and `U` are both unsized, the pointer is also
// returned unchanged. In particular, the metadata is
// preserved exactly.
//
// For instance, a cast from `*const [T]` to `*const [U]`
// preserves the number of elements. ... The same holds
// for str and any compound type whose unsized tail is a
// slice type, such as struct `Foo(i32, [u8])` or `(u64, Foo)`.
#[inline(always)]
fn raw_from_ptr_len(
bytes: ::zerocopy::util::macro_util::core_reexport::ptr::NonNull<u8>,
meta: Self::PointerMetadata,
) -> ::zerocopy::util::macro_util::core_reexport::ptr::NonNull<Self> {
use ::zerocopy::KnownLayout;
let trailing = <#trailing_field_ty as KnownLayout>::raw_from_ptr_len(bytes, meta);
let slf = trailing.as_ptr() as *mut Self;
// SAFETY: Constructed from `trailing`, which is non-null.
unsafe { ::zerocopy::util::macro_util::core_reexport::ptr::NonNull::new_unchecked(slf) }
}
#[inline(always)]
fn pointer_to_metadata(ptr: *mut Self) -> Self::PointerMetadata {
<#trailing_field_ty>::pointer_to_metadata(ptr as *mut _)
}
}
};
let inner_extras = {
let leading_fields_tys = leading_fields_tys.clone();
let methods = make_methods(*trailing_field_ty);
let (_, ty_generics, _) = ast.generics.split_for_impl();
quote!(
type PointerMetadata = <#trailing_field_ty as ::zerocopy::KnownLayout>::PointerMetadata;
type MaybeUninit = __ZerocopyKnownLayoutMaybeUninit #ty_generics;
// SAFETY: `LAYOUT` accurately describes the layout of `Self`.
// The layout of `Self` is reflected using a sequence of
// invocations of `DstLayout::{new_zst,extend,pad_to_align}`.
// The documentation of these items vows that invocations in
// this manner will acurately describe a type, so long as:
//
// - that type is `repr(C)`,
// - its fields are enumerated in the order they appear,
// - the presence of `repr_align` and `repr_packed` are correctly accounted for.
//
// We respect all three of these preconditions here. This
// expansion is only used if `is_repr_c_struct`, we enumerate
// the fields in order, and we extract the values of `align(N)`
// and `packed(N)`.
const LAYOUT: ::zerocopy::DstLayout = {
use ::zerocopy::util::macro_util::core_reexport::num::NonZeroUsize;
use ::zerocopy::{DstLayout, KnownLayout};
let repr_align = #repr_align;
let repr_packed = #repr_packed;
DstLayout::new_zst(repr_align)
#(.extend(DstLayout::for_type::<#leading_fields_tys>(), repr_packed))*
.extend(<#trailing_field_ty as KnownLayout>::LAYOUT, repr_packed)
.pad_to_align()
};
#methods
)
};
let outer_extras = {
let ident = &ast.ident;
let vis = &ast.vis;
let params = &ast.generics.params;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let predicates = if let Some(where_clause) = where_clause {
where_clause.predicates.clone()
} else {
Default::default()
};
// Generate a valid ident for a type-level handle to a field of a
// given `name`.
let field_index =
|name| Ident::new(&format!("__Zerocopy_Field_{}", name), ident.span());
let field_indices: Vec<_> =
fields.iter().map(|(_vis, name, _ty)| field_index(name)).collect();
// Define the collection of type-level field handles.
let field_defs = field_indices.iter().zip(&fields).map(|(idx, (vis, _, _))| {
quote! {
#[allow(non_camel_case_types)]
#vis struct #idx;
}
});
let field_impls = field_indices.iter().zip(&fields).map(|(idx, (_, _, ty))| quote! {
// SAFETY: `#ty` is the type of `#ident`'s field at `#idx`.
unsafe impl #impl_generics ::zerocopy::util::macro_util::Field<#idx> for #ident #ty_generics
where
#predicates
{
type Type = #ty;
}
});
let trailing_field_index = field_index(trailing_field_name);
let leading_field_indices =
leading_fields.iter().map(|(_vis, name, _ty)| field_index(name));
let trailing_field_ty = quote! {
<#ident #ty_generics as
::zerocopy::util::macro_util::Field<#trailing_field_index>
>::Type
};
let methods = make_methods(&parse_quote! {
<#trailing_field_ty as ::zerocopy::KnownLayout>::MaybeUninit
});
quote! {
#(#field_defs)*
#(#field_impls)*
// SAFETY: This has the same layout as the derive target type,
// except that it admits uninit bytes. This is ensured by using
// the same repr as the target type, and by using field types
// which have the same layout as the target type's fields,
// except that they admit uninit bytes. We indirect through
// `Field` to ensure that occurrences of `Self` resolve to
// `#ty`, not `__ZerocopyKnownLayoutMaybeUninit` (see #2116).
#repr
#[doc(hidden)]
// Required on some rustc versions due to a lint that is only
// triggered when `derive(KnownLayout)` is applied to `repr(C)`
// structs that are generated by macros. See #2177 for details.
#[allow(private_bounds)]
#vis struct __ZerocopyKnownLayoutMaybeUninit<#params> (
#(::zerocopy::util::macro_util::core_reexport::mem::MaybeUninit<
<#ident #ty_generics as
::zerocopy::util::macro_util::Field<#leading_field_indices>
>::Type
>,)*
// NOTE(#2302): We wrap in `ManuallyDrop` here in case the
// type we're operating on is both generic and
// `repr(packed)`. In that case, Rust needs to know that the
// type is *either* `Sized` or has a trivial `Drop`.
// `ManuallyDrop` has a trivial `Drop`, and so satisfies
// this requirement.
::zerocopy::util::macro_util::core_reexport::mem::ManuallyDrop<
<#trailing_field_ty as ::zerocopy::KnownLayout>::MaybeUninit
>
)
where
#trailing_field_ty: ::zerocopy::KnownLayout,
#predicates;
// SAFETY: We largely defer to the `KnownLayout` implementation on
// the derive target type (both by using the same tokens, and by
// deferring to impl via type-level indirection). This is sound,
// since `__ZerocopyKnownLayoutMaybeUninit` is guaranteed to
// have the same layout as the derive target type, except that
// `__ZerocopyKnownLayoutMaybeUninit` admits uninit bytes.
unsafe impl #impl_generics ::zerocopy::KnownLayout for __ZerocopyKnownLayoutMaybeUninit #ty_generics
where
#trailing_field_ty: ::zerocopy::KnownLayout,
#predicates
{
#[allow(clippy::missing_inline_in_public_items)]
fn only_derive_is_allowed_to_implement_this_trait() {}
type PointerMetadata = <#ident #ty_generics as ::zerocopy::KnownLayout>::PointerMetadata;
type MaybeUninit = Self;
const LAYOUT: ::zerocopy::DstLayout = <#ident #ty_generics as ::zerocopy::KnownLayout>::LAYOUT;
#methods
}
}
};
(SelfBounds::None, inner_extras, Some(outer_extras))
} else {
// For enums, unions, and non-`repr(C)` structs, we require that
// `Self` is sized, and as a result don't need to reason about the
// internals of the type.
(
SelfBounds::SIZED,
quote!(
type PointerMetadata = ();
type MaybeUninit =
::zerocopy::util::macro_util::core_reexport::mem::MaybeUninit<Self>;
// SAFETY: `LAYOUT` is guaranteed to accurately describe the
// layout of `Self`, because that is the documented safety
// contract of `DstLayout::for_type`.
const LAYOUT: ::zerocopy::DstLayout = ::zerocopy::DstLayout::for_type::<Self>();
// SAFETY: `.cast` preserves address and provenance.
//
// TODO(#429): Add documentation to `.cast` that promises that
// it preserves provenance.
#[inline(always)]
fn raw_from_ptr_len(
bytes: ::zerocopy::util::macro_util::core_reexport::ptr::NonNull<u8>,
_meta: (),
) -> ::zerocopy::util::macro_util::core_reexport::ptr::NonNull<Self>
{
bytes.cast::<Self>()
}
#[inline(always)]
fn pointer_to_metadata(_ptr: *mut Self) -> () {}
),
None,
)
};
Ok(match &ast.data {
Data::Struct(strct) => {
let require_trait_bound_on_field_types = if self_bounds == SelfBounds::SIZED {
FieldBounds::None
} else {
FieldBounds::TRAILING_SELF
};
// A bound on the trailing field is required, since structs are
// unsized if their trailing field is unsized. Reflecting the layout
// of an usized trailing field requires that the field is
// `KnownLayout`.
impl_block(
ast,
strct,
Trait::KnownLayout,
require_trait_bound_on_field_types,
self_bounds,
None,
Some(inner_extras),
outer_extras,
)
}
Data::Enum(enm) => {
// A bound on the trailing field is not required, since enums cannot
// currently be unsized.
impl_block(
ast,
enm,
Trait::KnownLayout,
FieldBounds::None,
SelfBounds::SIZED,
None,
Some(inner_extras),
outer_extras,
)
}
Data::Union(unn) => {
// A bound on the trailing field is not required, since unions
// cannot currently be unsized.
impl_block(
ast,
unn,
Trait::KnownLayout,
FieldBounds::None,
SelfBounds::SIZED,
None,
Some(inner_extras),
outer_extras,
)
}
})
}
fn derive_no_cell_inner(ast: &DeriveInput, _top_level: Trait) -> TokenStream {
match &ast.data {
Data::Struct(strct) => impl_block(
ast,
strct,
Trait::Immutable,
FieldBounds::ALL_SELF,
SelfBounds::None,
None,
None,
None,
),
Data::Enum(enm) => impl_block(
ast,
enm,
Trait::Immutable,
FieldBounds::ALL_SELF,
SelfBounds::None,
None,
None,
None,
),
Data::Union(unn) => impl_block(
ast,
unn,
Trait::Immutable,
FieldBounds::ALL_SELF,
SelfBounds::None,
None,
None,
None,
),
}
}
fn derive_try_from_bytes_inner(ast: &DeriveInput, top_level: Trait) -> Result<TokenStream, Error> {
match &ast.data {
Data::Struct(strct) => derive_try_from_bytes_struct(ast, strct, top_level),
Data::Enum(enm) => derive_try_from_bytes_enum(ast, enm, top_level),
Data::Union(unn) => Ok(derive_try_from_bytes_union(ast, unn, top_level)),
}
}
fn derive_from_zeros_inner(ast: &DeriveInput, top_level: Trait) -> Result<TokenStream, Error> {
let try_from_bytes = derive_try_from_bytes_inner(ast, top_level)?;
let from_zeros = match &ast.data {
Data::Struct(strct) => derive_from_zeros_struct(ast, strct),
Data::Enum(enm) => derive_from_zeros_enum(ast, enm)?,
Data::Union(unn) => derive_from_zeros_union(ast, unn),
};
Ok(IntoIterator::into_iter([try_from_bytes, from_zeros]).collect())
}
fn derive_from_bytes_inner(ast: &DeriveInput, top_level: Trait) -> Result<TokenStream, Error> {
let from_zeros = derive_from_zeros_inner(ast, top_level)?;
let from_bytes = match &ast.data {
Data::Struct(strct) => derive_from_bytes_struct(ast, strct),
Data::Enum(enm) => derive_from_bytes_enum(ast, enm)?,
Data::Union(unn) => derive_from_bytes_union(ast, unn),
};
Ok(IntoIterator::into_iter([from_zeros, from_bytes]).collect())
}
fn derive_into_bytes_inner(ast: &DeriveInput, _top_level: Trait) -> Result<TokenStream, Error> {
match &ast.data {
Data::Struct(strct) => derive_into_bytes_struct(ast, strct),
Data::Enum(enm) => derive_into_bytes_enum(ast, enm),
Data::Union(unn) => derive_into_bytes_union(ast, unn),
}
}
fn derive_unaligned_inner(ast: &DeriveInput, _top_level: Trait) -> Result<TokenStream, Error> {
match &ast.data {
Data::Struct(strct) => derive_unaligned_struct(ast, strct),
Data::Enum(enm) => derive_unaligned_enum(ast, enm),
Data::Union(unn) => derive_unaligned_union(ast, unn),
}
}
fn derive_hash_inner(ast: &DeriveInput, _top_level: Trait) -> Result<TokenStream, Error> {
// This doesn't delegate to `impl_block` because `impl_block` assumes it is deriving a
// `zerocopy`-defined trait, and these trait impls share a common shape that `Hash` does not.
// In particular, `zerocopy` traits contain a method that only `zerocopy_derive` macros
// are supposed to implement, and `impl_block` generating this trait method is incompatible
// with `Hash`.
let type_ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let where_predicates = where_clause.map(|clause| &clause.predicates);
Ok(quote! {
// TODO(#553): Add a test that generates a warning when
// `#[allow(deprecated)]` isn't present.
#[allow(deprecated)]
// While there are not currently any warnings that this suppresses (that
// we're aware of), it's good future-proofing hygiene.
#[automatically_derived]
impl #impl_generics ::zerocopy::util::macro_util::core_reexport::hash::Hash for #type_ident #ty_generics
where
Self: ::zerocopy::IntoBytes + ::zerocopy::Immutable,
#where_predicates
{
fn hash<H>(&self, state: &mut H)
where
H: ::zerocopy::util::macro_util::core_reexport::hash::Hasher,
{
::zerocopy::util::macro_util::core_reexport::hash::Hasher::write(
state,
::zerocopy::IntoBytes::as_bytes(self)
)
}
fn hash_slice<H>(data: &[Self], state: &mut H)
where
H: ::zerocopy::util::macro_util::core_reexport::hash::Hasher,
{
::zerocopy::util::macro_util::core_reexport::hash::Hasher::write(
state,
::zerocopy::IntoBytes::as_bytes(data)
)
}
}
})
}
fn derive_eq_inner(ast: &DeriveInput, _top_level: Trait) -> Result<TokenStream, Error> {
// This doesn't delegate to `impl_block` because `impl_block` assumes it is deriving a
// `zerocopy`-defined trait, and these trait impls share a common shape that `Eq` does not.
// In particular, `zerocopy` traits contain a method that only `zerocopy_derive` macros
// are supposed to implement, and `impl_block` generating this trait method is incompatible
// with `Eq`.
let type_ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let where_predicates = where_clause.map(|clause| &clause.predicates);
Ok(quote! {
// TODO(#553): Add a test that generates a warning when
// `#[allow(deprecated)]` isn't present.
#[allow(deprecated)]
// While there are not currently any warnings that this suppresses (that
// we're aware of), it's good future-proofing hygiene.
#[automatically_derived]
impl #impl_generics ::zerocopy::util::macro_util::core_reexport::cmp::PartialEq for #type_ident #ty_generics
where
Self: ::zerocopy::IntoBytes + ::zerocopy::Immutable,
#where_predicates
{
fn eq(&self, other: &Self) -> bool {
::zerocopy::util::macro_util::core_reexport::cmp::PartialEq::eq(
::zerocopy::IntoBytes::as_bytes(self),
::zerocopy::IntoBytes::as_bytes(other),
)
}
}
// TODO(#553): Add a test that generates a warning when
// `#[allow(deprecated)]` isn't present.
#[allow(deprecated)]
// While there are not currently any warnings that this suppresses (that
// we're aware of), it's good future-proofing hygiene.
#[automatically_derived]
impl #impl_generics ::zerocopy::util::macro_util::core_reexport::cmp::Eq for #type_ident #ty_generics
where
Self: ::zerocopy::IntoBytes + ::zerocopy::Immutable,
#where_predicates
{
}
})
}
/// A struct is `TryFromBytes` if:
/// - all fields are `TryFromBytes`
fn derive_try_from_bytes_struct(
ast: &DeriveInput,
strct: &DataStruct,
top_level: Trait,
) -> Result<TokenStream, Error> {
let extras = try_gen_trivial_is_bit_valid(ast, top_level).unwrap_or_else(|| {
let fields = strct.fields();
let field_names = fields.iter().map(|(_vis, name, _ty)| name);
let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
quote!(
// SAFETY: We use `is_bit_valid` to validate that each field is
// bit-valid, and only return `true` if all of them are. The bit
// validity of a struct is just the composition of the bit
// validities of its fields, so this is a sound implementation of
// `is_bit_valid`.
fn is_bit_valid<___ZerocopyAliasing>(
mut candidate: ::zerocopy::Maybe<Self, ___ZerocopyAliasing>,
) -> ::zerocopy::util::macro_util::core_reexport::primitive::bool
where
___ZerocopyAliasing: ::zerocopy::pointer::invariant::Reference,
{
true #(&& {
// SAFETY:
// - `project` is a field projection, and so it addresses a
// subset of the bytes addressed by `slf`
// - ..., and so it preserves provenance
// - ..., and `*slf` is a struct, so `UnsafeCell`s exist at
// the same byte ranges in the returned pointer's referent
// as they do in `*slf`
let field_candidate = unsafe {
let project = |slf: *mut Self|
::zerocopy::util::macro_util::core_reexport::ptr::addr_of_mut!((*slf).#field_names);
candidate.reborrow().cast_unsized_unchecked(project)
};
<#field_tys as ::zerocopy::TryFromBytes>::is_bit_valid(field_candidate)
})*
}
)
});
Ok(impl_block(
ast,
strct,
Trait::TryFromBytes,
FieldBounds::ALL_SELF,
SelfBounds::None,
None,
Some(extras),
None,
))
}
/// A union is `TryFromBytes` if:
/// - all of its fields are `TryFromBytes` and `Immutable`
fn derive_try_from_bytes_union(
ast: &DeriveInput,
unn: &DataUnion,
top_level: Trait,
) -> TokenStream {
// TODO(#5): Remove the `Immutable` bound.
let field_type_trait_bounds =
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Immutable)]);
let extras = try_gen_trivial_is_bit_valid(ast, top_level).unwrap_or_else(|| {
let fields = unn.fields();
let field_names = fields.iter().map(|(_vis, name, _ty)| name);
let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
quote!(
// SAFETY: We use `is_bit_valid` to validate that any field is
// bit-valid; we only return `true` if at least one of them is. The
// bit validity of a union is not yet well defined in Rust, but it
// is guaranteed to be no more strict than this definition. See #696
// for a more in-depth discussion.
fn is_bit_valid<___ZerocopyAliasing>(
mut candidate: ::zerocopy::Maybe<'_, Self,___ZerocopyAliasing>
) -> ::zerocopy::util::macro_util::core_reexport::primitive::bool
where
___ZerocopyAliasing: ::zerocopy::pointer::invariant::Reference,
{
false #(|| {
// SAFETY:
// - `project` is a field projection, and so it addresses a
// subset of the bytes addressed by `slf`
// - ..., and so it preserves provenance
// - Since `Self: Immutable` is enforced by
// `self_type_trait_bounds`, neither `*slf` nor the
// returned pointer's referent contain any `UnsafeCell`s
let field_candidate = unsafe {
let project = |slf: *mut Self|
::zerocopy::util::macro_util::core_reexport::ptr::addr_of_mut!((*slf).#field_names);
candidate.reborrow().cast_unsized_unchecked(project)
};
<#field_tys as ::zerocopy::TryFromBytes>::is_bit_valid(field_candidate)
})*
}
)
});
impl_block(
ast,
unn,
Trait::TryFromBytes,
field_type_trait_bounds,
SelfBounds::None,
None,
Some(extras),
None,
)
}
fn derive_try_from_bytes_enum(
ast: &DeriveInput,
enm: &DataEnum,
top_level: Trait,
) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ast.attrs)?;
// If an enum has no fields, it has a well-defined integer representation,
// and every possible bit pattern corresponds to a valid discriminant tag,
// then it *could* be `FromBytes` (even if the user hasn't derived
// `FromBytes`). This holds if, for `repr(uN)` or `repr(iN)`, there are 2^N
// variants.
let could_be_from_bytes = enum_size_from_repr(&repr)
.map(|size| enm.fields().is_empty() && enm.variants.len() == 1usize << size)
.unwrap_or(false);
let trivial_is_bit_valid = try_gen_trivial_is_bit_valid(ast, top_level);
let extra = match (trivial_is_bit_valid, could_be_from_bytes) {
(Some(is_bit_valid), _) => is_bit_valid,
// SAFETY: It would be sound for the enum to implement `FomBytes`, as
// required by `gen_trivial_is_bit_valid_unchecked`.
(None, true) => unsafe { gen_trivial_is_bit_valid_unchecked() },
(None, false) => r#enum::derive_is_bit_valid(&ast.ident, &repr, &ast.generics, enm)?,
};
Ok(impl_block(
ast,
enm,
Trait::TryFromBytes,
FieldBounds::ALL_SELF,
SelfBounds::None,
None,
Some(extra),
None,
))
}
/// Attempts to generate a `TryFromBytes::is_bit_valid` instance that
/// unconditionally returns true.
///
/// This is possible when the `top_level` trait is `FromBytes` and there are no
/// generic type parameters. In this case, we know that compilation will succeed
/// only if the type is unconditionally `FromBytes`. Type parameters are not
/// supported because a type with type parameters could be `TryFromBytes` but
/// not `FromBytes` depending on its type parameters, and so deriving a trivial
/// `is_bit_valid` would be either unsound or, assuming we add a defensive
/// `Self: FromBytes` bound (as we currently do), overly restrictive. Consider,
/// for example, that `Foo<bool>` ought to be `TryFromBytes` but not `FromBytes`
/// in this example:
///
/// ```rust,ignore
/// #[derive(FromBytes)]
/// #[repr(transparent)]
/// struct Foo<T>(T);
/// ```
///
/// This should be used where possible. Using this impl is faster to codegen,
/// faster to compile, and is friendlier on the optimizer.
fn try_gen_trivial_is_bit_valid(
ast: &DeriveInput,
top_level: Trait,
) -> Option<proc_macro2::TokenStream> {
// If the top-level trait is `FromBytes` and `Self` has no type parameters,
// then the `FromBytes` derive will fail compilation if `Self` is not
// actually soundly `FromBytes`, and so we can rely on that for our
// `is_bit_valid` impl. It's plausible that we could make changes - or Rust
// could make changes (such as the "trivial bounds" language feature) - that
// make this no longer true. To hedge against these, we include an explicit
// `Self: FromBytes` check in the generated `is_bit_valid`, which is
// bulletproof.
if top_level == Trait::FromBytes && ast.generics.params.is_empty() {
Some(quote!(
// SAFETY: See inline.
fn is_bit_valid<___ZerocopyAliasing>(
_candidate: ::zerocopy::Maybe<Self, ___ZerocopyAliasing>,
) -> ::zerocopy::util::macro_util::core_reexport::primitive::bool
where
___ZerocopyAliasing: ::zerocopy::pointer::invariant::Reference,
{
if false {
fn assert_is_from_bytes<T>()
where
T: ::zerocopy::FromBytes,
T: ?::zerocopy::util::macro_util::core_reexport::marker::Sized,
{
}
assert_is_from_bytes::<Self>();
}
// SAFETY: The preceding code only compiles if `Self:
// FromBytes`. Thus, this code only compiles if all initialized
// byte sequences represent valid instances of `Self`.
true
}
))
} else {
None
}
}
/// Generates a `TryFromBytes::is_bit_valid` instance that unconditionally
/// returns true.
///
/// This should be used where possible, (although `try_gen_trivial_is_bit_valid`
/// should be preferred over this for safety reasons). Using this impl is faster
/// to codegen, faster to compile, and is friendlier on the optimizer.
///
/// # Safety
///
/// The caller must ensure that all initialized bit patterns are valid for
/// `Self`.
unsafe fn gen_trivial_is_bit_valid_unchecked() -> proc_macro2::TokenStream {
quote!(
// SAFETY: The caller of `gen_trivial_is_bit_valid_unchecked` has
// promised that all initialized bit patterns are valid for `Self`.
fn is_bit_valid<___ZerocopyAliasing>(
_candidate: ::zerocopy::Maybe<Self, ___ZerocopyAliasing>,
) -> ::zerocopy::util::macro_util::core_reexport::primitive::bool
where
___ZerocopyAliasing: ::zerocopy::pointer::invariant::Reference,
{
true
}
)
}
/// A struct is `FromZeros` if:
/// - all fields are `FromZeros`
fn derive_from_zeros_struct(ast: &DeriveInput, strct: &DataStruct) -> TokenStream {
impl_block(
ast,
strct,
Trait::FromZeros,
FieldBounds::ALL_SELF,
SelfBounds::None,
None,
None,
None,
)
}
/// Returns `Ok(index)` if variant `index` of the enum has a discriminant of
/// zero. If `Err(bool)` is returned, the boolean is true if the enum has
/// unknown discriminants (e.g. discriminants set to const expressions which we
/// can't evaluate in a proc macro). If the enum has unknown discriminants, then
/// it might have a zero variant that we just can't detect.
fn find_zero_variant(enm: &DataEnum) -> Result<usize, bool> {
// Discriminants can be anywhere in the range [i128::MIN, u128::MAX] because
// the discriminant type may be signed or unsigned. Since we only care about
// tracking the discriminant when it's less than or equal to zero, we can
// avoid u128 -> i128 conversions and bounds checking by making the "next
// discriminant" value implicitly negative.
// Technically 64 bits is enough, but 128 is better for future compatibility
// with https://github.com/rust-lang/rust/issues/56071
let mut next_negative_discriminant = Some(0);
// Sometimes we encounter explicit discriminants that we can't know the
// value of (e.g. a constant expression that requires evaluation). These
// could evaluate to zero or a negative number, but we can't assume that
// they do (no false positives allowed!). So we treat them like strictly-
// positive values that can't result in any zero variants, and track whether
// we've encountered any unknown discriminants.
let mut has_unknown_discriminants = false;
for (i, v) in enm.variants.iter().enumerate() {
match v.discriminant.as_ref() {
// Implicit discriminant
None => {
match next_negative_discriminant.as_mut() {
Some(0) => return Ok(i),
// n is nonzero so subtraction is always safe
Some(n) => *n -= 1,
None => (),
}
}
// Explicit positive discriminant
Some((_, Expr::Lit(ExprLit { lit: Lit::Int(int), .. }))) => {
match int.base10_parse::<u128>().ok() {
Some(0) => return Ok(i),
Some(_) => next_negative_discriminant = None,
None => {
// Numbers should never fail to parse, but just in case:
has_unknown_discriminants = true;
next_negative_discriminant = None;
}
}
}
// Explicit negative discriminant
Some((_, Expr::Unary(ExprUnary { op: UnOp::Neg(_), expr, .. }))) => match &**expr {
Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) => {
match int.base10_parse::<u128>().ok() {
Some(0) => return Ok(i),
// x is nonzero so subtraction is always safe
Some(x) => next_negative_discriminant = Some(x - 1),
None => {
// Numbers should never fail to parse, but just in
// case:
has_unknown_discriminants = true;
next_negative_discriminant = None;
}
}
}
// Unknown negative discriminant (e.g. const repr)
_ => {
has_unknown_discriminants = true;
next_negative_discriminant = None;
}
},
// Unknown discriminant (e.g. const expr)
_ => {
has_unknown_discriminants = true;
next_negative_discriminant = None;
}
}
}
Err(has_unknown_discriminants)
}
/// An enum is `FromZeros` if:
/// - one of the variants has a discriminant of `0`
/// - that variant's fields are all `FromZeros`
fn derive_from_zeros_enum(ast: &DeriveInput, enm: &DataEnum) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ast.attrs)?;
// We don't actually care what the repr is; we just care that it's one of
// the allowed ones.
match repr {
Repr::Compound(
Spanned { t: CompoundRepr::C | CompoundRepr::Primitive(_), span: _ },
_,
) => {}
Repr::Transparent(_)
| Repr::Compound(Spanned { t: CompoundRepr::Rust, span: _ }, _) => return Err(Error::new(Span::call_site(), "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout")),
}
let zero_variant = match find_zero_variant(enm) {
Ok(index) => enm.variants.iter().nth(index).unwrap(),
// Has unknown variants
Err(true) => {
return Err(Error::new_spanned(
ast,
"FromZeros only supported on enums with a variant that has a discriminant of `0`\n\
help: This enum has discriminants which are not literal integers. One of those may \
define or imply which variant has a discriminant of zero. Use a literal integer to \
define or imply the variant with a discriminant of zero.",
));
}
// Does not have unknown variants
Err(false) => {
return Err(Error::new_spanned(
ast,
"FromZeros only supported on enums with a variant that has a discriminant of `0`",
));
}
};
let explicit_bounds = zero_variant
.fields
.iter()
.map(|field| {
let ty = &field.ty;
parse_quote! { #ty: ::zerocopy::FromZeros }
})
.collect::<Vec<WherePredicate>>();
Ok(impl_block(
ast,
enm,
Trait::FromZeros,
FieldBounds::Explicit(explicit_bounds),
SelfBounds::None,
None,
None,
None,
))
}
/// Unions are `FromZeros` if
/// - all fields are `FromZeros` and `Immutable`
fn derive_from_zeros_union(ast: &DeriveInput, unn: &DataUnion) -> TokenStream {
// TODO(#5): Remove the `Immutable` bound. It's only necessary for
// compatibility with `derive(TryFromBytes)` on unions; not for soundness.
let field_type_trait_bounds =
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Immutable)]);
impl_block(
ast,
unn,
Trait::FromZeros,
field_type_trait_bounds,
SelfBounds::None,
None,
None,
None,
)
}
/// A struct is `FromBytes` if:
/// - all fields are `FromBytes`
fn derive_from_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> TokenStream {
impl_block(
ast,
strct,
Trait::FromBytes,
FieldBounds::ALL_SELF,
SelfBounds::None,
None,
None,
None,
)
}
/// An enum is `FromBytes` if:
/// - Every possible bit pattern must be valid, which means that every bit
/// pattern must correspond to a different enum variant. Thus, for an enum
/// whose layout takes up N bytes, there must be 2^N variants.
/// - Since we must know N, only representations which guarantee the layout's
/// size are allowed. These are `repr(uN)` and `repr(iN)` (`repr(C)` implies an
/// implementation-defined size). `usize` and `isize` technically guarantee the
/// layout's size, but would require us to know how large those are on the
/// target platform. This isn't terribly difficult - we could emit a const
/// expression that could call `core::mem::size_of` in order to determine the
/// size and check against the number of enum variants, but a) this would be
/// platform-specific and, b) even on Rust's smallest bit width platform (32),
/// this would require ~4 billion enum variants, which obviously isn't a thing.
/// - All fields of all variants are `FromBytes`.
fn derive_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ast.attrs)?;
let variants_required = 1usize << enum_size_from_repr(&repr)?;
if enm.variants.len() != variants_required {
return Err(Error::new_spanned(
ast,
format!(
"FromBytes only supported on {} enum with {} variants",
repr.repr_type_name(),
variants_required
),
));
}
Ok(impl_block(
ast,
enm,
Trait::FromBytes,
FieldBounds::ALL_SELF,
SelfBounds::None,
None,
None,
None,
))
}
// Returns `None` if the enum's size is not guaranteed by the repr.
fn enum_size_from_repr(repr: &EnumRepr) -> Result<usize, Error> {
use {CompoundRepr::*, PrimitiveRepr::*, Repr::*};
match repr {
Transparent(span)
| Compound(
Spanned { t: C | Rust | Primitive(U32 | I32 | U64 | I64 | Usize | Isize), span },
_,
) => Err(Error::new(*span, "`FromBytes` only supported on enums with `#[repr(...)]` attributes `u8`, `i8`, `u16`, or `i16`")),
Compound(Spanned { t: Primitive(U8 | I8), span: _ }, _align) => Ok(8),
Compound(Spanned { t: Primitive(U16 | I16), span: _ }, _align) => Ok(16),
}
}
/// Unions are `FromBytes` if
/// - all fields are `FromBytes` and `Immutable`
fn derive_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> TokenStream {
// TODO(#5): Remove the `Immutable` bound. It's only necessary for
// compatibility with `derive(TryFromBytes)` on unions; not for soundness.
let field_type_trait_bounds =
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Immutable)]);
impl_block(
ast,
unn,
Trait::FromBytes,
field_type_trait_bounds,
SelfBounds::None,
None,
None,
None,
)
}
fn derive_into_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&ast.attrs)?;
let is_transparent = repr.is_transparent();
let is_c = repr.is_c();
let is_packed_1 = repr.is_packed_1();
let num_fields = strct.fields().len();
let (padding_check, require_unaligned_fields) = if is_transparent || is_packed_1 {
// No padding check needed.
// - repr(transparent): The layout and ABI of the whole struct is the
// same as its only non-ZST field (meaning there's no padding outside
// of that field) and we require that field to be `IntoBytes` (meaning
// there's no padding in that field).
// - repr(packed): Any inter-field padding bytes are removed, meaning
// that any padding bytes would need to come from the fields, all of
// which we require to be `IntoBytes` (meaning they don't have any
// padding). Note that this holds regardless of other `repr`
// attributes, including `repr(Rust)`. [1]
//
// [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#the-alignment-modifiers:
//
// An important consequence of these rules is that a type with
// `#[repr(packed(1))]`` (or `#[repr(packed)]``) will have no
// inter-field padding.
(None, false)
} else if is_c && !repr.is_align_gt_1() && num_fields <= 1 {
// No padding check needed. A repr(C) struct with zero or one field has
// no padding unless #[repr(align)] explicitly adds padding, which we
// check for in this branch's condition.
(None, false)
} else if ast.generics.params.is_empty() {
// Since there are no generics, we can emit a padding check. All reprs
// guarantee that fields won't overlap [1], so the padding check is
// sound. This is more permissive than the next case, which requires
// that all field types implement `Unaligned`.
//
// [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#the-rust-representation:
//
// The only data layout guarantees made by [`repr(Rust)`] are those
// required for soundness. They are:
// ...
// 2. The fields do not overlap.
// ...
(Some(PaddingCheck::Struct), false)
} else if is_c && !repr.is_align_gt_1() {
// We can't use a padding check since there are generic type arguments.
// Instead, we require all field types to implement `Unaligned`. This
// ensures that the `repr(C)` layout algorithm will not insert any
// padding unless #[repr(align)] explicitly adds padding, which we check
// for in this branch's condition.
//
// TODO(#10): Support type parameters for non-transparent, non-packed
// structs without requiring `Unaligned`.
(None, true)
} else {
return Err(Error::new(Span::call_site(), "must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout"));
};
let field_bounds = if require_unaligned_fields {
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Unaligned)])
} else {
FieldBounds::ALL_SELF
};
Ok(impl_block(
ast,
strct,
Trait::IntoBytes,
field_bounds,
SelfBounds::None,
padding_check,
None,
None,
))
}
/// If the type is an enum:
/// - It must have a defined representation (`repr`s `C`, `u8`, `u16`, `u32`,
/// `u64`, `usize`, `i8`, `i16`, `i32`, `i64`, or `isize`).
/// - It must have no padding bytes.
/// - Its fields must be `IntoBytes`.
fn derive_into_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ast.attrs)?;
if !repr.is_c() && !repr.is_primitive() {
return Err(Error::new(Span::call_site(), "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout"));
}
let tag_type_definition = r#enum::generate_tag_enum(&repr, enm);
Ok(impl_block(
ast,
enm,
Trait::IntoBytes,
FieldBounds::ALL_SELF,
SelfBounds::None,
Some(PaddingCheck::Enum { tag_type_definition }),
None,
None,
))
}
/// A union is `IntoBytes` if:
/// - all fields are `IntoBytes`
/// - `repr(C)`, `repr(transparent)`, or `repr(packed)`
/// - no padding (size of union equals size of each field type)
fn derive_into_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> Result<TokenStream, Error> {
// See #1792 for more context.
//
// By checking for `zerocopy_derive_union_into_bytes` both here and in the
// generated code, we ensure that `--cfg zerocopy_derive_union_into_bytes`
// need only be passed *either* when compiling this crate *or* when
// compiling the user's crate. The former is preferable, but in some
// situations (such as when cross-compiling using `cargo build --target`),
// it doesn't get propagated to this crate's build by default.
let cfg_compile_error = if cfg!(zerocopy_derive_union_into_bytes) {
quote!()
} else {
quote!(
const _: () = {
#[cfg(not(zerocopy_derive_union_into_bytes))]
::zerocopy::util::macro_util::core_reexport::compile_error!(
"requires --cfg zerocopy_derive_union_into_bytes;
please let us know you use this feature: https://github.com/google/zerocopy/discussions/1802"
);
};
)
};
// TODO(#10): Support type parameters.
if !ast.generics.params.is_empty() {
return Err(Error::new(Span::call_site(), "unsupported on types with type parameters"));
}
// Because we don't support generics, we don't need to worry about
// special-casing different reprs. So long as there is *some* repr which
// guarantees the layout, our `PaddingCheck::Union` guarantees that there is
// no padding.
let repr = StructUnionRepr::from_attrs(&ast.attrs)?;
if !repr.is_c() && !repr.is_transparent() && !repr.is_packed_1() {
return Err(Error::new(
Span::call_site(),
"must be #[repr(C)], #[repr(packed)], or #[repr(transparent)]",
));
}
let impl_block = impl_block(
ast,
unn,
Trait::IntoBytes,
FieldBounds::ALL_SELF,
SelfBounds::None,
Some(PaddingCheck::Union),
None,
None,
);
Ok(quote!(#cfg_compile_error #impl_block))
}
/// A struct is `Unaligned` if:
/// - `repr(align)` is no more than 1 and either
/// - `repr(C)` or `repr(transparent)` and
/// - all fields `Unaligned`
/// - `repr(packed)`
fn derive_unaligned_struct(ast: &DeriveInput, strct: &DataStruct) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&ast.attrs)?;
repr.unaligned_validate_no_align_gt_1()?;
let field_bounds = if repr.is_packed_1() {
FieldBounds::None
} else if repr.is_c() || repr.is_transparent() {
FieldBounds::ALL_SELF
} else {
return Err(Error::new(Span::call_site(), "must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment"));
};
Ok(impl_block(ast, strct, Trait::Unaligned, field_bounds, SelfBounds::None, None, None, None))
}
/// An enum is `Unaligned` if:
/// - No `repr(align(N > 1))`
/// - `repr(u8)` or `repr(i8)`
fn derive_unaligned_enum(ast: &DeriveInput, enm: &DataEnum) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ast.attrs)?;
repr.unaligned_validate_no_align_gt_1()?;
if !repr.is_u8() && !repr.is_i8() {
return Err(Error::new(Span::call_site(), "must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment"));
}
Ok(impl_block(
ast,
enm,
Trait::Unaligned,
FieldBounds::ALL_SELF,
SelfBounds::None,
None,
None,
None,
))
}
/// Like structs, a union is `Unaligned` if:
/// - `repr(align)` is no more than 1 and either
/// - `repr(C)` or `repr(transparent)` and
/// - all fields `Unaligned`
/// - `repr(packed)`
fn derive_unaligned_union(ast: &DeriveInput, unn: &DataUnion) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&ast.attrs)?;
repr.unaligned_validate_no_align_gt_1()?;
let field_type_trait_bounds = if repr.is_packed_1() {
FieldBounds::None
} else if repr.is_c() || repr.is_transparent() {
FieldBounds::ALL_SELF
} else {
return Err(Error::new(Span::call_site(), "must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment"));
};
Ok(impl_block(
ast,
unn,
Trait::Unaligned,
field_type_trait_bounds,
SelfBounds::None,
None,
None,
None,
))
}
/// This enum describes what kind of padding check needs to be generated for the
/// associated impl.
enum PaddingCheck {
/// Check that the sum of the fields' sizes exactly equals the struct's
/// size.
Struct,
/// Check that the size of each field exactly equals the union's size.
Union,
/// Check that every variant of the enum contains no padding.
///
/// Because doing so requires a tag enum, this padding check requires an
/// additional `TokenStream` which defines the tag enum as `___ZerocopyTag`.
Enum { tag_type_definition: TokenStream },
}
impl PaddingCheck {
/// Returns the ident of the macro to call in order to validate that a type
/// passes the padding check encoded by `PaddingCheck`.
fn validator_macro_ident(&self) -> Ident {
let s = match self {
PaddingCheck::Struct => "struct_has_padding",
PaddingCheck::Union => "union_has_padding",
PaddingCheck::Enum { .. } => "enum_has_padding",
};
Ident::new(s, Span::call_site())
}
/// Sometimes performing the padding check requires some additional
/// "context" code. For enums, this is the definition of the tag enum.
fn validator_macro_context(&self) -> Option<&TokenStream> {
match self {
PaddingCheck::Struct | PaddingCheck::Union => None,
PaddingCheck::Enum { tag_type_definition } => Some(tag_type_definition),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Trait {
KnownLayout,
Immutable,
TryFromBytes,
FromZeros,
FromBytes,
IntoBytes,
Unaligned,
Sized,
ByteHash,
ByteEq,
}
impl ToTokens for Trait {
fn to_tokens(&self, tokens: &mut TokenStream) {
// According to [1], the format of the derived `Debug`` output is not
// stable and therefore not guaranteed to represent the variant names.
// Indeed with the (unstable) `fmt-debug` compiler flag [2], it can
// return only a minimalized output or empty string. To make sure this
// code will work in the future and independet of the compiler flag, we
// translate the variants to their names manually here.
//
// [1] https://doc.rust-lang.org/1.81.0/std/fmt/trait.Debug.html#stability
// [2] https://doc.rust-lang.org/beta/unstable-book/compiler-flags/fmt-debug.html
let s = match self {
Trait::KnownLayout => "KnownLayout",
Trait::Immutable => "Immutable",
Trait::TryFromBytes => "TryFromBytes",
Trait::FromZeros => "FromZeros",
Trait::FromBytes => "FromBytes",
Trait::IntoBytes => "IntoBytes",
Trait::Unaligned => "Unaligned",
Trait::Sized => "Sized",
Trait::ByteHash => "ByteHash",
Trait::ByteEq => "ByteEq",
};
let ident = Ident::new(s, Span::call_site());
tokens.extend(core::iter::once(TokenTree::Ident(ident)));
}
}
impl Trait {
fn crate_path(&self) -> Path {
match self {
Self::Sized => parse_quote!(::zerocopy::util::macro_util::core_reexport::marker::#self),
_ => parse_quote!(::zerocopy::#self),
}
}
}
#[derive(Debug, Eq, PartialEq)]
enum TraitBound {
Slf,
Other(Trait),
}
enum FieldBounds<'a> {
None,
All(&'a [TraitBound]),
Trailing(&'a [TraitBound]),
Explicit(Vec<WherePredicate>),
}
impl<'a> FieldBounds<'a> {
const ALL_SELF: FieldBounds<'a> = FieldBounds::All(&[TraitBound::Slf]);
const TRAILING_SELF: FieldBounds<'a> = FieldBounds::Trailing(&[TraitBound::Slf]);
}
#[derive(Debug, Eq, PartialEq)]
enum SelfBounds<'a> {
None,
All(&'a [Trait]),
}
// TODO(https://github.com/rust-lang/rust-clippy/issues/12908): This is a false positive.
// Explicit lifetimes are actually necessary here.
#[allow(clippy::needless_lifetimes)]
impl<'a> SelfBounds<'a> {
const SIZED: Self = Self::All(&[Trait::Sized]);
}
/// Normalizes a slice of bounds by replacing [`TraitBound::Slf`] with `slf`.
fn normalize_bounds(slf: Trait, bounds: &[TraitBound]) -> impl '_ + Iterator<Item = Trait> {
bounds.iter().map(move |bound| match bound {
TraitBound::Slf => slf,
TraitBound::Other(trt) => *trt,
})
}
#[allow(clippy::too_many_arguments)]
fn impl_block<D: DataExt>(
input: &DeriveInput,
data: &D,
trt: Trait,
field_type_trait_bounds: FieldBounds,
self_type_trait_bounds: SelfBounds,
padding_check: Option<PaddingCheck>,
inner_extras: Option<TokenStream>,
outer_extras: Option<TokenStream>,
) -> TokenStream {
// In this documentation, we will refer to this hypothetical struct:
//
// #[derive(FromBytes)]
// struct Foo<T, I: Iterator>
// where
// T: Copy,
// I: Clone,
// I::Item: Clone,
// {
// a: u8,
// b: T,
// c: I::Item,
// }
//
// We extract the field types, which in this case are `u8`, `T`, and
// `I::Item`. We re-use the existing parameters and where clauses. If
// `require_trait_bound == true` (as it is for `FromBytes), we add where
// bounds for each field's type:
//
// impl<T, I: Iterator> FromBytes for Foo<T, I>
// where
// T: Copy,
// I: Clone,
// I::Item: Clone,
// T: FromBytes,
// I::Item: FromBytes,
// {
// }
//
// NOTE: It is standard practice to only emit bounds for the type parameters
// themselves, not for field types based on those parameters (e.g., `T` vs
// `T::Foo`). For a discussion of why this is standard practice, see
// https://github.com/rust-lang/rust/issues/26925.
//
// The reason we diverge from this standard is that doing it that way for us
// would be unsound. E.g., consider a type, `T` where `T: FromBytes` but
// `T::Foo: !FromBytes`. It would not be sound for us to accept a type with
// a `T::Foo` field as `FromBytes` simply because `T: FromBytes`.
//
// While there's no getting around this requirement for us, it does have the
// pretty serious downside that, when lifetimes are involved, the trait
// solver ties itself in knots:
//
// #[derive(Unaligned)]
// #[repr(C)]
// struct Dup<'a, 'b> {
// a: PhantomData<&'a u8>,
// b: PhantomData<&'b u8>,
// }
//
// error[E0283]: type annotations required: cannot resolve `core::marker::PhantomData<&'a u8>: zerocopy::Unaligned`
// --> src/main.rs:6:10
// |
// 6 | #[derive(Unaligned)]
// | ^^^^^^^^^
// |
// = note: required by `zerocopy::Unaligned`
let type_ident = &input.ident;
let trait_path = trt.crate_path();
let fields = data.fields();
let variants = data.variants();
let tag = data.tag();
fn bound_tt(ty: &Type, traits: impl Iterator<Item = Trait>) -> WherePredicate {
let traits = traits.map(|t| t.crate_path());
parse_quote!(#ty: #(#traits)+*)
}
let field_type_bounds: Vec<_> = match (field_type_trait_bounds, &fields[..]) {
(FieldBounds::All(traits), _) => fields
.iter()
.map(|(_vis, _name, ty)| bound_tt(ty, normalize_bounds(trt, traits)))
.collect(),
(FieldBounds::None, _) | (FieldBounds::Trailing(..), []) => vec![],
(FieldBounds::Trailing(traits), [.., last]) => {
vec![bound_tt(last.2, normalize_bounds(trt, traits))]
}
(FieldBounds::Explicit(bounds), _) => bounds,
};
// Don't bother emitting a padding check if there are no fields.
#[allow(unstable_name_collisions)] // See `BoolExt` below
// Work around https://github.com/rust-lang/rust-clippy/issues/12280
#[allow(clippy::incompatible_msrv)]
let padding_check_bound =
padding_check.and_then(|check| (!fields.is_empty()).then_some(check)).map(|check| {
let variant_types = variants.iter().map(|var| {
let types = var.iter().map(|(_vis, _name, ty)| ty);
quote!([#(#types),*])
});
let validator_context = check.validator_macro_context();
let validator_macro = check.validator_macro_ident();
let t = tag.iter();
parse_quote! {
(): ::zerocopy::util::macro_util::PaddingFree<
Self,
{
#validator_context
::zerocopy::#validator_macro!(Self, #(#t,)* #(#variant_types),*)
}
>
}
});
let self_bounds: Option<WherePredicate> = match self_type_trait_bounds {
SelfBounds::None => None,
SelfBounds::All(traits) => Some(bound_tt(&parse_quote!(Self), traits.iter().copied())),
};
let bounds = input
.generics
.where_clause
.as_ref()
.map(|where_clause| where_clause.predicates.iter())
.into_iter()
.flatten()
.chain(field_type_bounds.iter())
.chain(padding_check_bound.iter())
.chain(self_bounds.iter());
// The parameters with trait bounds, but without type defaults.
let params = input.generics.params.clone().into_iter().map(|mut param| {
match &mut param {
GenericParam::Type(ty) => ty.default = None,
GenericParam::Const(cnst) => cnst.default = None,
GenericParam::Lifetime(_) => {}
}
quote!(#param)
});
// The identifiers of the parameters without trait bounds or type defaults.
let param_idents = input.generics.params.iter().map(|param| match param {
GenericParam::Type(ty) => {
let ident = &ty.ident;
quote!(#ident)
}
GenericParam::Lifetime(l) => {
let ident = &l.lifetime;
quote!(#ident)
}
GenericParam::Const(cnst) => {
let ident = &cnst.ident;
quote!({#ident})
}
});
let impl_tokens = quote! {
// TODO(#553): Add a test that generates a warning when
// `#[allow(deprecated)]` isn't present.
#[allow(deprecated)]
// While there are not currently any warnings that this suppresses (that
// we're aware of), it's good future-proofing hygiene.
#[automatically_derived]
unsafe impl < #(#params),* > #trait_path for #type_ident < #(#param_idents),* >
where
#(#bounds,)*
{
fn only_derive_is_allowed_to_implement_this_trait() {}
#inner_extras
}
};
if let Some(outer_extras) = outer_extras {
// So that any items defined in `#outer_extras` don't conflict with
// existing names defined in this scope.
quote! {
const _: () = {
#impl_tokens
#outer_extras
};
}
} else {
impl_tokens
}
}
// A polyfill for `Option::then_some`, which was added after our MSRV.
//
// The `#[allow(unused)]` is necessary because, on sufficiently recent toolchain
// versions, `b.then_some(...)` resolves to the inherent method rather than to
// this trait, and so this trait is considered unused.
//
// TODO(#67): Remove this once our MSRV is >= 1.62.
#[allow(unused)]
trait BoolExt {
fn then_some<T>(self, t: T) -> Option<T>;
}
impl BoolExt for bool {
fn then_some<T>(self, t: T) -> Option<T> {
if self {
Some(t)
} else {
None
}
}
}