Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,192 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
//
use proc_macro2::{Span, TokenStream};
use syn::{
parse_quote, Data, DataEnum, DataStruct, DataUnion, Error, Expr, ExprLit, ExprUnary, Lit, UnOp,
WherePredicate,
};
use crate::{
derive::try_from_bytes::derive_try_from_bytes,
repr::{CompoundRepr, EnumRepr, Repr, Spanned},
util::{enum_size_from_repr, Ctx, FieldBounds, ImplBlockBuilder, Trait, TraitBound},
};
/// 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.
pub(crate) 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)
}
pub(crate) fn derive_from_zeros(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
let try_from_bytes = derive_try_from_bytes(ctx, top_level)?;
let from_zeros = match &ctx.ast.data {
Data::Struct(strct) => derive_from_zeros_struct(ctx, strct),
Data::Enum(enm) => derive_from_zeros_enum(ctx, enm)?,
Data::Union(unn) => derive_from_zeros_union(ctx, unn),
};
Ok(IntoIterator::into_iter([try_from_bytes, from_zeros]).collect())
}
pub(crate) fn derive_from_bytes(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
let from_zeros = derive_from_zeros(ctx, top_level)?;
let from_bytes = match &ctx.ast.data {
Data::Struct(strct) => derive_from_bytes_struct(ctx, strct),
Data::Enum(enm) => derive_from_bytes_enum(ctx, enm)?,
Data::Union(unn) => derive_from_bytes_union(ctx, unn),
};
Ok(IntoIterator::into_iter([from_zeros, from_bytes]).collect())
}
fn derive_from_zeros_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream {
ImplBlockBuilder::new(ctx, strct, Trait::FromZeros, FieldBounds::ALL_SELF).build()
}
fn derive_from_zeros_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ctx.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 ctx.error_or_skip(
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 ctx.error_or_skip(Error::new_spanned(
&ctx.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 ctx.error_or_skip(Error::new_spanned(
&ctx.ast,
"FromZeros only supported on enums with a variant that has a discriminant of `0`",
));
}
};
let zerocopy_crate = &ctx.zerocopy_crate;
let explicit_bounds = zero_variant
.fields
.iter()
.map(|field| {
let ty = &field.ty;
parse_quote! { #ty: #zerocopy_crate::FromZeros }
})
.collect::<Vec<WherePredicate>>();
Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromZeros, FieldBounds::Explicit(explicit_bounds))
.build())
}
fn derive_from_zeros_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream {
let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
ImplBlockBuilder::new(ctx, unn, Trait::FromZeros, field_type_trait_bounds).build()
}
fn derive_from_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream {
ImplBlockBuilder::new(ctx, strct, Trait::FromBytes, FieldBounds::ALL_SELF).build()
}
fn derive_from_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
let variants_required = 1usize << enum_size_from_repr(&repr)?;
if enm.variants.len() != variants_required {
return ctx.error_or_skip(Error::new_spanned(
&ctx.ast,
format!(
"FromBytes only supported on {} enum with {} variants",
repr.repr_type_name(),
variants_required
),
));
}
Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromBytes, FieldBounds::ALL_SELF).build())
}
fn derive_from_bytes_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream {
let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
ImplBlockBuilder::new(ctx, unn, Trait::FromBytes, field_type_trait_bounds).build()
}
@@ -0,0 +1,164 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
//
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{Data, DataEnum, DataStruct, DataUnion, Error, Type};
use crate::{
repr::{EnumRepr, StructUnionRepr},
util::{
generate_tag_enum, Ctx, DataExt, FieldBounds, ImplBlockBuilder, PaddingCheck, Trait,
TraitBound,
},
};
pub(crate) fn derive_into_bytes(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
match &ctx.ast.data {
Data::Struct(strct) => derive_into_bytes_struct(ctx, strct),
Data::Enum(enm) => derive_into_bytes_enum(ctx, enm),
Data::Union(unn) => derive_into_bytes_union(ctx, unn),
}
}
fn derive_into_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&ctx.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 ctx.ast.generics.params.is_empty() {
// Is the last field a syntactic slice, i.e., `[SomeType]`.
let is_syntactic_dst =
strct.fields().last().map(|(_, _, ty)| matches!(ty, Type::Slice(_))).unwrap_or(false);
// 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.
// ...
if is_c && is_syntactic_dst {
(Some(PaddingCheck::ReprCStruct), false)
} else {
(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.
//
// FIXME(#10): Support type parameters for non-transparent, non-packed
// structs without requiring `Unaligned`.
(None, true)
} else {
return ctx.error_or_skip(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(ImplBlockBuilder::new(ctx, strct, Trait::IntoBytes, field_bounds)
.padding_check(padding_check)
.build())
}
fn derive_into_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
if !repr.is_c() && !repr.is_primitive() {
return ctx.error_or_skip(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 = generate_tag_enum(ctx, &repr, enm);
Ok(ImplBlockBuilder::new(ctx, enm, Trait::IntoBytes, FieldBounds::ALL_SELF)
.padding_check(PaddingCheck::Enum { tag_type_definition })
.build())
}
fn derive_into_bytes_union(ctx: &Ctx, 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 {
let core = ctx.core_path();
let error_message = "requires --cfg zerocopy_derive_union_into_bytes;
please let us know you use this feature: https://github.com/google/zerocopy/discussions/1802";
quote!(
#[allow(unused_attributes, unexpected_cfgs)]
const _: () = {
#[cfg(not(zerocopy_derive_union_into_bytes))]
#core::compile_error!(#error_message);
};
)
};
// FIXME(#10): Support type parameters.
if !ctx.ast.generics.params.is_empty() {
return ctx.error_or_skip(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(&ctx.ast.attrs)?;
if !repr.is_c() && !repr.is_transparent() && !repr.is_packed_1() {
return ctx.error_or_skip(Error::new(
Span::call_site(),
"must be #[repr(C)], #[repr(packed)], or #[repr(transparent)]",
));
}
let impl_block = ImplBlockBuilder::new(ctx, unn, Trait::IntoBytes, FieldBounds::ALL_SELF)
.padding_check(PaddingCheck::Union)
.build();
Ok(quote!(#cfg_compile_error #impl_block))
}
@@ -0,0 +1,349 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
//
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_quote, Data, Error, Type};
use crate::{
repr::StructUnionRepr,
util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, SelfBounds, Trait},
};
fn derive_known_layout_for_repr_c_struct<'a>(
ctx: &'a Ctx,
repr: &StructUnionRepr,
fields: &[(&'a syn::Visibility, TokenStream, &'a Type)],
) -> Option<(SelfBounds<'a>, TokenStream, Option<TokenStream>)> {
let (trailing_field, leading_fields) = 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 = ctx.core_path();
let repr_align = repr
.get_align()
.map(|align| {
let align = align.t.get();
quote!(#core::num::NonZeroUsize::new(#align as usize))
})
.unwrap_or_else(|| quote!(#core::option::Option::None));
let repr_packed = repr
.get_packed()
.map(|packed| {
let packed = packed.get();
quote!(#core::num::NonZeroUsize::new(#packed as usize))
})
.unwrap_or_else(|| quote!(#core::option::Option::None));
let zerocopy_crate = &ctx.zerocopy_crate;
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: #core::ptr::NonNull<u8>,
meta: <Self as #zerocopy_crate::KnownLayout>::PointerMetadata,
) -> #core::ptr::NonNull<Self> {
let trailing = <#trailing_field_ty as #zerocopy_crate::KnownLayout>::raw_from_ptr_len(bytes, meta);
let slf = trailing.as_ptr() as *mut Self;
// SAFETY: Constructed from `trailing`, which is non-null.
unsafe { #core::ptr::NonNull::new_unchecked(slf) }
}
#[inline(always)]
fn pointer_to_metadata(ptr: *mut Self) -> <Self as #zerocopy_crate::KnownLayout>::PointerMetadata {
<#trailing_field_ty>::pointer_to_metadata(ptr as *mut _)
}
}
};
let inner_extras = {
let methods = make_methods(*trailing_field_ty);
let (_, ty_generics, _) = ctx.ast.generics.split_for_impl();
quote!(
type PointerMetadata = <#trailing_field_ty as #zerocopy_crate::KnownLayout>::PointerMetadata;
type MaybeUninit = __ZerocopyKnownLayoutMaybeUninit #ty_generics;
// SAFETY: `LAYOUT` accurately describes the layout of `Self`.
// The documentation of `DstLayout::for_repr_c_struct` vows that
// invocations in this manner will accurately 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_crate::DstLayout = #zerocopy_crate::DstLayout::for_repr_c_struct(
#repr_align,
#repr_packed,
&[
#(#zerocopy_crate::DstLayout::for_type::<#leading_fields_tys>(),)*
<#trailing_field_ty as #zerocopy_crate::KnownLayout>::LAYOUT
],
);
#methods
)
};
let outer_extras = {
let ident = &ctx.ast.ident;
let vis = &ctx.ast.vis;
let params = &ctx.ast.generics.params;
let (impl_generics, ty_generics, where_clause) = ctx.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: &TokenStream| ident!(("__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! {
#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`.
//
// We implement `Field` for each field of the struct to create a
// projection from the field index to its type. This allows us
// to refer to the field's type in a way that respects `Self`
// hygiene. If we just copy-pasted the tokens of `#ty`, we
// would not respect `Self` hygiene, as `Self` would refer to
// the helper struct we are generating, not the derive target
// type.
unsafe impl #impl_generics #zerocopy_crate::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));
// We use `Field` to project the type of the trailing field. This is
// required to ensure that if the field type uses `Self`, it
// resolves to the derive target type, not the helper struct we are
// generating.
let trailing_field_ty = quote! {
<#ident #ty_generics as
#zerocopy_crate::util::macro_util::Field<#trailing_field_index>
>::Type
};
let methods = make_methods(&parse_quote! {
<#trailing_field_ty as #zerocopy_crate::KnownLayout>::MaybeUninit
});
let core = ctx.core_path();
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)]
#vis struct __ZerocopyKnownLayoutMaybeUninit<#params> (
#(#core::mem::MaybeUninit<
<#ident #ty_generics as
#zerocopy_crate::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.
#core::mem::ManuallyDrop<
<#trailing_field_ty as #zerocopy_crate::KnownLayout>::MaybeUninit
>
)
where
#trailing_field_ty: #zerocopy_crate::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_crate::KnownLayout for __ZerocopyKnownLayoutMaybeUninit #ty_generics
where
#trailing_field_ty: #zerocopy_crate::KnownLayout,
#predicates
{
fn only_derive_is_allowed_to_implement_this_trait() {}
type PointerMetadata = <#ident #ty_generics as #zerocopy_crate::KnownLayout>::PointerMetadata;
type MaybeUninit = Self;
const LAYOUT: #zerocopy_crate::DstLayout = <#ident #ty_generics as #zerocopy_crate::KnownLayout>::LAYOUT;
#methods
}
}
};
Some((SelfBounds::None, inner_extras, Some(outer_extras)))
}
pub(crate) fn derive(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
// If this is a `repr(C)` struct, then `c_struct_repr` contains the entire
// `repr` attribute.
let c_struct_repr = match &ctx.ast.data {
Data::Struct(..) => {
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
if repr.is_c() {
Some(repr)
} else {
None
}
}
Data::Enum(..) | Data::Union(..) => None,
};
let fields = ctx.ast.data.fields();
let (self_bounds, inner_extras, outer_extras) = c_struct_repr
.as_ref()
.and_then(|repr| {
derive_known_layout_for_repr_c_struct(ctx, repr, &fields)
})
.unwrap_or_else(|| {
let zerocopy_crate = &ctx.zerocopy_crate;
let core = ctx.core_path();
// 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 =
#core::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_crate::DstLayout = #zerocopy_crate::DstLayout::for_type::<Self>();
// SAFETY: `.cast` preserves address and provenance.
//
// FIXME(#429): Add documentation to `.cast` that promises that
// it preserves provenance.
#[inline(always)]
fn raw_from_ptr_len(bytes: #core::ptr::NonNull<u8>, _meta: ()) -> #core::ptr::NonNull<Self> {
bytes.cast::<Self>()
}
#[inline(always)]
fn pointer_to_metadata(_ptr: *mut Self) -> () {}
),
None,
)
});
Ok(match &ctx.ast.data {
Data::Struct(strct) => {
let require_trait_bound_on_field_types =
if matches!(self_bounds, SelfBounds::All(&[Trait::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`.
ImplBlockBuilder::new(
ctx,
strct,
Trait::KnownLayout,
require_trait_bound_on_field_types,
)
.self_type_trait_bounds(self_bounds)
.inner_extras(inner_extras)
.outer_extras(outer_extras)
.build()
}
Data::Enum(enm) => {
// A bound on the trailing field is not required, since enums cannot
// currently be unsized.
ImplBlockBuilder::new(ctx, enm, Trait::KnownLayout, FieldBounds::None)
.self_type_trait_bounds(SelfBounds::SIZED)
.inner_extras(inner_extras)
.outer_extras(outer_extras)
.build()
}
Data::Union(unn) => {
// A bound on the trailing field is not required, since unions
// cannot currently be unsized.
ImplBlockBuilder::new(ctx, unn, Trait::KnownLayout, FieldBounds::None)
.self_type_trait_bounds(SelfBounds::SIZED)
.inner_extras(inner_extras)
.outer_extras(outer_extras)
.build()
}
})
}
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
//
pub mod from_bytes;
pub mod into_bytes;
pub mod known_layout;
pub mod try_from_bytes;
pub mod unaligned;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{Data, Error};
use crate::{
repr::StructUnionRepr,
util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, Trait},
};
pub(crate) fn derive_immutable(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
Ok(match &ctx.ast.data {
Data::Struct(strct) => {
ImplBlockBuilder::new(ctx, strct, Trait::Immutable, FieldBounds::ALL_SELF).build()
}
Data::Enum(enm) => {
ImplBlockBuilder::new(ctx, enm, Trait::Immutable, FieldBounds::ALL_SELF).build()
}
Data::Union(unn) => {
ImplBlockBuilder::new(ctx, unn, Trait::Immutable, FieldBounds::ALL_SELF).build()
}
})
}
pub(crate) fn derive_hash(ctx: &Ctx, _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 = &ctx.ast.ident;
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
let where_predicates = where_clause.map(|clause| &clause.predicates);
let zerocopy_crate = &ctx.zerocopy_crate;
let core = ctx.core_path();
Ok(quote! {
impl #impl_generics #core::hash::Hash for #type_ident #ty_generics
where
Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
#where_predicates
{
fn hash<H: #core::hash::Hasher>(&self, state: &mut H) {
#core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(self))
}
fn hash_slice<H: #core::hash::Hasher>(data: &[Self], state: &mut H) {
#core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(data))
}
}
})
}
pub(crate) fn derive_eq(ctx: &Ctx, _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 = &ctx.ast.ident;
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
let where_predicates = where_clause.map(|clause| &clause.predicates);
let zerocopy_crate = &ctx.zerocopy_crate;
let core = ctx.core_path();
Ok(quote! {
impl #impl_generics #core::cmp::PartialEq for #type_ident #ty_generics
where
Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
#where_predicates
{
fn eq(&self, other: &Self) -> bool {
#core::cmp::PartialEq::eq(
#zerocopy_crate::IntoBytes::as_bytes(self),
#zerocopy_crate::IntoBytes::as_bytes(other),
)
}
}
impl #impl_generics #core::cmp::Eq for #type_ident #ty_generics
where
Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
#where_predicates
{
}
})
}
pub(crate) fn derive_split_at(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
match &ctx.ast.data {
Data::Struct(_) => {}
Data::Enum(_) | Data::Union(_) => {
return ctx
.error_or_skip(Error::new(Span::call_site(), "can only be applied to structs"));
}
};
if repr.get_packed().is_some() {
return ctx.error_or_skip(Error::new(
Span::call_site(),
"must not have #[repr(packed)] attribute",
));
}
if !(repr.is_c() || repr.is_transparent()) {
return ctx.error_or_skip(Error::new(
Span::call_site(),
"must have #[repr(C)] or #[repr(transparent)] in order to guarantee this type's layout is splitable",
));
}
let fields = ctx.ast.data.fields();
let trailing_field = if let Some(((_, _, trailing_field), _)) = fields.split_last() {
trailing_field
} else {
return ctx.error_or_skip(Error::new(Span::call_site(), "must at least one field"));
};
let zerocopy_crate = &ctx.zerocopy_crate;
// SAFETY: `#ty`, per the above checks, is `repr(C)` or `repr(transparent)`
// and is not packed; its trailing field is guaranteed to be well-aligned
// for its type. By invariant on `FieldBounds::TRAILING_SELF`, the trailing
// slice of the trailing field is also well-aligned for its type.
Ok(ImplBlockBuilder::new(ctx, &ctx.ast.data, Trait::SplitAt, FieldBounds::TRAILING_SELF)
.inner_extras(quote! {
type Elem = <#trailing_field as #zerocopy_crate::SplitAt>::Elem;
})
.build())
}
@@ -0,0 +1,765 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
//
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
parse_quote, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error,
Expr, Fields, Ident, Index, Type,
};
use crate::{
repr::{EnumRepr, StructUnionRepr},
util::{
const_block, enum_size_from_repr, generate_tag_enum, Ctx, DataExt, FieldBounds,
ImplBlockBuilder, Trait, TraitBound,
},
};
fn tag_ident(variant_ident: &Ident) -> Ident {
ident!(("___ZEROCOPY_TAG_{}", variant_ident), variant_ident.span())
}
/// Generates a constant for the tag associated with each variant of the enum.
/// When we match on the enum's tag, each arm matches one of these constants. We
/// have to use constants here because:
///
/// - The type that we're matching on is not the type of the tag, it's an
/// integer of the same size as the tag type and with the same bit patterns.
/// - We can't read the enum tag as an enum because the bytes may not represent
/// a valid variant.
/// - Patterns do not currently support const expressions, so we have to assign
/// these constants to names rather than use them inline in the `match`
/// statement.
fn generate_tag_consts(data: &DataEnum) -> TokenStream {
let tags = data.variants.iter().map(|v| {
let variant_ident = &v.ident;
let tag_ident = tag_ident(variant_ident);
quote! {
// This casts the enum variant to its discriminant, and then
// converts the discriminant to the target integral type via a
// numeric cast [1].
//
// Because these are the same size, this is defined to be a no-op
// and therefore is a lossless conversion [2].
//
// [1] Per https://doc.rust-lang.org/1.81.0/reference/expressions/operator-expr.html#enum-cast:
//
// Casts an enum to its discriminant.
//
// [2] Per https://doc.rust-lang.org/1.81.0/reference/expressions/operator-expr.html#numeric-cast:
//
// Casting between two integers of the same size (e.g. i32 -> u32)
// is a no-op.
const #tag_ident: ___ZerocopyTagPrimitive =
___ZerocopyTag::#variant_ident as ___ZerocopyTagPrimitive;
}
});
quote! {
#(#tags)*
}
}
fn variant_struct_ident(variant_ident: &Ident) -> Ident {
ident!(("___ZerocopyVariantStruct_{}", variant_ident), variant_ident.span())
}
/// Generates variant structs for the given enum variant.
///
/// These are structs associated with each variant of an enum. They are
/// `repr(C)` tuple structs with the same fields as the variant after a
/// `MaybeUninit<___ZerocopyInnerTag>`.
///
/// In order to unify the generated types for `repr(C)` and `repr(int)` enums,
/// we use a "fused" representation with fields for both an inner tag and an
/// outer tag. Depending on the repr, we will set one of these tags to the tag
/// type and the other to `()`. This lets us generate the same code but put the
/// tags in different locations.
fn generate_variant_structs(ctx: &Ctx, data: &DataEnum) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
let enum_name = &ctx.ast.ident;
// All variant structs have a `PhantomData<MyEnum<...>>` field because we
// don't know which generic parameters each variant will use, and unused
// generic parameters are a compile error.
let core = ctx.core_path();
let phantom_ty = quote! {
#core::marker::PhantomData<#enum_name #ty_generics>
};
let variant_structs = data.variants.iter().filter_map(|variant| {
// We don't generate variant structs for unit variants because we only
// need to check the tag. This helps cut down our generated code a bit.
if matches!(variant.fields, Fields::Unit) {
return None;
}
let variant_struct_ident = variant_struct_ident(&variant.ident);
let field_types = variant.fields.iter().map(|f| &f.ty);
let variant_struct = parse_quote! {
#[repr(C)]
struct #variant_struct_ident #impl_generics (
#core::mem::MaybeUninit<___ZerocopyInnerTag>,
#(#field_types,)*
#phantom_ty,
) #where_clause;
};
// We do this rather than emitting `#[derive(::zerocopy::TryFromBytes)]`
// because that is not hygienic, and this is also more performant.
let try_from_bytes_impl =
derive_try_from_bytes(&ctx.with_input(&variant_struct), Trait::TryFromBytes)
.expect("derive_try_from_bytes should not fail on synthesized type");
Some(quote! {
#variant_struct
#try_from_bytes_impl
})
});
quote! {
#(#variant_structs)*
}
}
fn variants_union_field_ident(ident: &Ident) -> Ident {
// Field names are prefixed with `__field_` to prevent name collision
// with the `__nonempty` field.
ident!(("__field_{}", ident), ident.span())
}
fn generate_variants_union(ctx: &Ctx, data: &DataEnum) -> TokenStream {
let generics = &ctx.ast.generics;
let (_, ty_generics, _) = generics.split_for_impl();
let fields = data.variants.iter().filter_map(|variant| {
// We don't generate variant structs for unit variants because we only
// need to check the tag. This helps cut down our generated code a bit.
if matches!(variant.fields, Fields::Unit) {
return None;
}
let field_name = variants_union_field_ident(&variant.ident);
let variant_struct_ident = variant_struct_ident(&variant.ident);
let core = ctx.core_path();
Some(quote! {
#field_name: #core::mem::ManuallyDrop<#variant_struct_ident #ty_generics>,
})
});
let variants_union = parse_quote! {
#[repr(C)]
union ___ZerocopyVariants #generics {
#(#fields)*
// Enums can have variants with no fields, but unions must
// have at least one field. So we just add a trailing unit
// to ensure that this union always has at least one field.
// Because this union is `repr(C)`, this unit type does not
// affect the layout.
__nonempty: (),
}
};
let has_field =
derive_has_field_struct_union(&ctx.with_input(&variants_union), &variants_union.data);
quote! {
#variants_union
#has_field
}
}
/// Generates an implementation of `is_bit_valid` for an arbitrary enum.
///
/// The general process is:
///
/// 1. Generate a tag enum. This is an enum with the same repr, variants, and
/// corresponding discriminants as the original enum, but without any fields
/// on the variants. This gives us access to an enum where the variants have
/// the same discriminants as the one we're writing `is_bit_valid` for.
/// 2. Make constants from the variants of the tag enum. We need these because
/// we can't put const exprs in match arms.
/// 3. Generate variant structs. These are structs which have the same fields as
/// each variant of the enum, and are `#[repr(C)]` with an optional "inner
/// tag".
/// 4. Generate a variants union, with one field for each variant struct type.
/// 5. And finally, our raw enum is a `#[repr(C)]` struct of an "outer tag" and
/// the variants union.
///
/// See these reference links for fully-worked example decompositions.
///
/// - `repr(C)`: <https://doc.rust-lang.org/reference/type-layout.html#reprc-enums-with-fields>
/// - `repr(int)`: <https://doc.rust-lang.org/reference/type-layout.html#primitive-representation-of-enums-with-fields>
/// - `repr(C, int)`: <https://doc.rust-lang.org/reference/type-layout.html#combining-primitive-representations-of-enums-with-fields-and-reprc>
pub(crate) fn derive_is_bit_valid(
ctx: &Ctx,
data: &DataEnum,
repr: &EnumRepr,
) -> Result<TokenStream, Error> {
let trait_path = Trait::TryFromBytes.crate_path(ctx);
let tag_enum = generate_tag_enum(ctx, repr, data);
let tag_consts = generate_tag_consts(data);
let (outer_tag_type, inner_tag_type) = if repr.is_c() {
(quote! { ___ZerocopyTag }, quote! { () })
} else if repr.is_primitive() {
(quote! { () }, quote! { ___ZerocopyTag })
} else {
return Err(Error::new(
ctx.ast.span(),
"must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
));
};
let variant_structs = generate_variant_structs(ctx, data);
let variants_union = generate_variants_union(ctx, data);
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
let zerocopy_crate = &ctx.zerocopy_crate;
let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag, FieldBounds::None)
.inner_extras(quote! {
type Tag = ___ZerocopyTag;
type ProjectToTag = #zerocopy_crate::pointer::cast::CastSized;
})
.build();
let has_fields = data.variants().into_iter().flat_map(|(variant, fields)| {
let variant_ident = &variant.unwrap().ident;
let variants_union_field_ident = variants_union_field_ident(variant_ident);
let field: Box<syn::Type> = parse_quote!(());
fields.into_iter().enumerate().map(move |(idx, (vis, ident, ty))| {
// Rust does not presently support explicit visibility modifiers on
// enum fields, but we guard against the possibility to ensure this
// derive remains sound.
assert!(matches!(vis, syn::Visibility::Inherited));
let variant_struct_field_index = Index::from(idx + 1);
let (_, ty_generics, _) = ctx.ast.generics.split_for_impl();
let has_field_trait = Trait::HasField {
variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
// Since Rust does not presently support explicit visibility
// modifiers on enum fields, any public type is suitable here;
// we use `()`.
field: field.clone(),
field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
};
let has_field_path = has_field_trait.crate_path(ctx);
let has_field = ImplBlockBuilder::new(
ctx,
data,
has_field_trait,
FieldBounds::None,
)
.inner_extras(quote! {
type Type = #ty;
#[inline(always)]
fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut <Self as #has_field_path>::Type {
use #zerocopy_crate::pointer::cast::{CastSized, Projection};
slf.project::<___ZerocopyRawEnum #ty_generics, CastSized>()
.project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(variants) }>>()
.project::<_, Projection<_, { #zerocopy_crate::REPR_C_UNION_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variants_union_field_ident) }>>()
.project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(value) }>>()
.project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variant_struct_field_index) }>>()
.as_ptr()
}
})
.build();
let project = ImplBlockBuilder::new(
ctx,
data,
Trait::ProjectField {
variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
// Since Rust does not presently support explicit visibility
// modifiers on enum fields, any public type is suitable
// here; we use `()`.
field: field.clone(),
field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
invariants: parse_quote!((Aliasing, Alignment, #zerocopy_crate::invariant::Initialized)),
},
FieldBounds::None,
)
.param_extras(vec![
parse_quote!(Aliasing: #zerocopy_crate::invariant::Aliasing),
parse_quote!(Alignment: #zerocopy_crate::invariant::Alignment),
])
.inner_extras(quote! {
type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
type Invariants = (Aliasing, Alignment, #zerocopy_crate::invariant::Initialized);
})
.build();
quote! {
#has_field
#project
}
})
});
let core = ctx.core_path();
let match_arms = data.variants.iter().map(|variant| {
let tag_ident = tag_ident(&variant.ident);
let variant_struct_ident = variant_struct_ident(&variant.ident);
let variants_union_field_ident = variants_union_field_ident(&variant.ident);
if matches!(variant.fields, Fields::Unit) {
// Unit variants don't need any further validation beyond checking
// the tag.
quote! {
#tag_ident => true
}
} else {
quote! {
#tag_ident => {
// SAFETY: Since we know that the tag is `#tag_ident`, we
// know that no other `&`s exist which refer to this enum
// as any other variant.
let variant_md = variants.cast::<
_,
#zerocopy_crate::pointer::cast::Projection<
// #zerocopy_crate::ReadOnly<_>,
_,
{ #zerocopy_crate::REPR_C_UNION_VARIANT_ID },
{ #zerocopy_crate::ident_id!(#variants_union_field_ident) }
>,
_
>();
let variant = variant_md.cast::<
#zerocopy_crate::ReadOnly<#variant_struct_ident #ty_generics>,
#zerocopy_crate::pointer::cast::CastSized,
(#zerocopy_crate::pointer::BecauseRead, _)
>();
<
#variant_struct_ident #ty_generics as #trait_path
>::is_bit_valid(variant)
}
}
}
});
let generics = &ctx.ast.generics;
let raw_enum: DeriveInput = parse_quote! {
#[repr(C)]
struct ___ZerocopyRawEnum #generics {
tag: ___ZerocopyOuterTag,
variants: ___ZerocopyVariants #ty_generics,
}
};
let self_ident = &ctx.ast.ident;
let invariants_eq_impl = quote! {
// SAFETY: `___ZerocopyRawEnum` is designed to have the same layout,
// validity, and invariants as `Self`.
unsafe impl #impl_generics #zerocopy_crate::pointer::InvariantsEq<___ZerocopyRawEnum #ty_generics> for #self_ident #ty_generics #where_clause {}
};
let raw_enum_projections =
derive_has_field_struct_union(&ctx.with_input(&raw_enum), &raw_enum.data);
let raw_enum = quote! {
#raw_enum
#invariants_eq_impl
#raw_enum_projections
};
Ok(quote! {
// SAFETY: We use `is_bit_valid` to validate that the bit pattern of the
// enum's tag corresponds to one of the enum's discriminants. Then, we
// check the bit validity of each field of the corresponding variant.
// Thus, this is a sound implementation of `is_bit_valid`.
#[inline]
fn is_bit_valid<___ZcAlignment>(
mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
) -> #core::primitive::bool
where
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
{
#tag_enum
type ___ZerocopyTagPrimitive = #zerocopy_crate::util::macro_util::SizeToTag<
{ #core::mem::size_of::<___ZerocopyTag>() },
>;
#tag_consts
type ___ZerocopyOuterTag = #outer_tag_type;
type ___ZerocopyInnerTag = #inner_tag_type;
#variant_structs
#variants_union
#raw_enum
#has_tag
#(#has_fields)*
let tag = {
// SAFETY:
// - The provided cast addresses a subset of the bytes addressed
// by `candidate` because it addresses the starting tag of the
// enum.
// - Because the pointer is cast from `candidate`, it has the
// same provenance as it.
// - There are no `UnsafeCell`s in the tag because it is a
// primitive integer.
// - `tag_ptr` is casted from `candidate`, whose referent is
// `Initialized`. Since we have not written uninitialized
// bytes into the referent, `tag_ptr` is also `Initialized`.
//
// FIXME(#2874): Revise this to a `cast` once `candidate`
// references a `ReadOnly<Self>`.
let tag_ptr = unsafe {
candidate.reborrow().project_transmute_unchecked::<
_,
#zerocopy_crate::invariant::Initialized,
#zerocopy_crate::pointer::cast::CastSized
>()
};
tag_ptr.recall_validity::<_, (_, (_, _))>().read::<#zerocopy_crate::BecauseImmutable>()
};
let mut raw_enum = candidate.cast::<
#zerocopy_crate::ReadOnly<___ZerocopyRawEnum #ty_generics>,
#zerocopy_crate::pointer::cast::CastSized,
(#zerocopy_crate::pointer::BecauseRead, _)
>();
let variants = #zerocopy_crate::into_inner!(raw_enum.project::<
_,
{ #zerocopy_crate::STRUCT_VARIANT_ID },
{ #zerocopy_crate::ident_id!(variants) }
>());
match tag {
#(#match_arms,)*
_ => false,
}
}
})
}
pub(crate) fn derive_try_from_bytes(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
match &ctx.ast.data {
Data::Struct(strct) => derive_try_from_bytes_struct(ctx, strct, top_level),
Data::Enum(enm) => derive_try_from_bytes_enum(ctx, enm, top_level),
Data::Union(unn) => Ok(derive_try_from_bytes_union(ctx, unn, top_level)),
}
}
fn derive_has_field_struct_union(ctx: &Ctx, data: &dyn DataExt) -> TokenStream {
let fields = ctx.ast.data.fields();
if fields.is_empty() {
return quote! {};
}
let field_tokens = fields.iter().map(|(vis, ident, _)| {
let ident = ident!(("__z{}", ident), ident.span());
quote!(
#vis enum #ident {}
)
});
let zerocopy_crate = &ctx.zerocopy_crate;
let variant_id: Box<Expr> = match &ctx.ast.data {
Data::Struct(_) => parse_quote!({ #zerocopy_crate::STRUCT_VARIANT_ID }),
Data::Union(_) => {
let is_repr_c = StructUnionRepr::from_attrs(&ctx.ast.attrs)
.map(|repr| repr.is_c())
.unwrap_or(false);
if is_repr_c {
parse_quote!({ #zerocopy_crate::REPR_C_UNION_VARIANT_ID })
} else {
parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID })
}
}
_ => unreachable!(),
};
let core = ctx.core_path();
let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag, FieldBounds::None)
.inner_extras(quote! {
type Tag = ();
type ProjectToTag = #zerocopy_crate::pointer::cast::CastToUnit;
})
.build();
let has_fields = fields.iter().map(move |(_, ident, ty)| {
let field_token = ident!(("__z{}", ident), ident.span());
let field: Box<Type> = parse_quote!(#field_token);
let field_id: Box<Expr> = parse_quote!({ #zerocopy_crate::ident_id!(#ident) });
let has_field_trait = Trait::HasField {
variant_id: variant_id.clone(),
field: field.clone(),
field_id: field_id.clone(),
};
let has_field_path = has_field_trait.crate_path(ctx);
ImplBlockBuilder::new(
ctx,
data,
has_field_trait,
FieldBounds::None,
)
.inner_extras(quote! {
type Type = #ty;
#[inline(always)]
fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut <Self as #has_field_path>::Type {
let slf = slf.as_ptr();
// SAFETY: By invariant on `PtrInner`, `slf` is a non-null
// pointer whose referent is zero-sized or lives in a valid
// allocation. Since `#ident` is a struct or union field of
// `Self`, this projection preserves or shrinks the referent
// size, and so the resulting referent also fits in the same
// allocation.
unsafe { #core::ptr::addr_of_mut!((*slf).#ident) }
}
}).outer_extras(if matches!(&ctx.ast.data, Data::Struct(..)) {
let fields_preserve_alignment = StructUnionRepr::from_attrs(&ctx.ast.attrs)
.map(|repr| repr.get_packed().is_none())
.unwrap();
let alignment = if fields_preserve_alignment {
quote! { Alignment }
} else {
quote! { #zerocopy_crate::invariant::Unaligned }
};
// SAFETY: See comments on items.
ImplBlockBuilder::new(
ctx,
data,
Trait::ProjectField {
variant_id: variant_id.clone(),
field,
field_id,
invariants: parse_quote!((Aliasing, Alignment, #zerocopy_crate::invariant::Initialized)),
},
FieldBounds::None,
)
.param_extras(vec![
parse_quote!(Aliasing: #zerocopy_crate::invariant::Aliasing),
parse_quote!(Alignment: #zerocopy_crate::invariant::Alignment),
])
.inner_extras(quote! {
// SAFETY: Projection into structs is always infallible.
type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
// SAFETY: The alignment of the projected `Ptr` is `Unaligned`
// if the structure is packed; otherwise inherited from the
// outer `Ptr`. If the validity of the outer pointer is
// `Initialized`, so too is the validity of its fields.
type Invariants = (Aliasing, #alignment, #zerocopy_crate::invariant::Initialized);
})
.build()
} else {
quote! {}
})
.build()
});
const_block(field_tokens.into_iter().chain(Some(has_tag)).chain(has_fields).map(Some))
}
fn derive_try_from_bytes_struct(
ctx: &Ctx,
strct: &DataStruct,
top_level: Trait,
) -> Result<TokenStream, Error> {
let extras = try_gen_trivial_is_bit_valid(ctx, top_level).unwrap_or_else(|| {
let zerocopy_crate = &ctx.zerocopy_crate;
let fields = strct.fields();
let field_names = fields.iter().map(|(_vis, name, _ty)| name);
let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
let core = ctx.core_path();
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`.
#[inline]
fn is_bit_valid<___ZcAlignment>(
mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
) -> #core::primitive::bool
where
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
{
true #(&& {
let field_candidate = #zerocopy_crate::into_inner!(candidate.reborrow().project::<
_,
{ #zerocopy_crate::STRUCT_VARIANT_ID },
{ #zerocopy_crate::ident_id!(#field_names) }
>());
<#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
})*
}
)
});
Ok(ImplBlockBuilder::new(ctx, strct, Trait::TryFromBytes, FieldBounds::ALL_SELF)
.inner_extras(extras)
.outer_extras(derive_has_field_struct_union(ctx, strct))
.build())
}
fn derive_try_from_bytes_union(ctx: &Ctx, unn: &DataUnion, top_level: Trait) -> TokenStream {
let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
let zerocopy_crate = &ctx.zerocopy_crate;
let variant_id: Box<Expr> = {
let is_repr_c =
StructUnionRepr::from_attrs(&ctx.ast.attrs).map(|repr| repr.is_c()).unwrap_or(false);
if is_repr_c {
parse_quote!({ #zerocopy_crate::REPR_C_UNION_VARIANT_ID })
} else {
parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID })
}
};
let extras = try_gen_trivial_is_bit_valid(ctx, 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);
let core = ctx.core_path();
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.
#[inline]
fn is_bit_valid<___ZcAlignment>(
mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
) -> #core::primitive::bool
where
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
{
false #(|| {
// SAFETY:
// - Since `ReadOnly<Self>: Immutable` unconditionally,
// neither `*slf` nor the returned pointer's referent
// permit interior mutation.
// - Both source and destination validity are
// `Initialized`, which is always a sound
// transmutation.
let field_candidate = unsafe {
candidate.reborrow().project_transmute_unchecked::<
_,
_,
#zerocopy_crate::pointer::cast::Projection<
_,
#variant_id,
{ #zerocopy_crate::ident_id!(#field_names) }
>
>()
};
<#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
})*
}
)
});
ImplBlockBuilder::new(ctx, unn, Trait::TryFromBytes, field_type_trait_bounds)
.inner_extras(extras)
.outer_extras(derive_has_field_struct_union(ctx, unn))
.build()
}
fn derive_try_from_bytes_enum(
ctx: &Ctx,
enm: &DataEnum,
top_level: Trait,
) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ctx.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(ctx, 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 `FromBytes`, as
// required by `gen_trivial_is_bit_valid_unchecked`.
(None, true) => unsafe { gen_trivial_is_bit_valid_unchecked(ctx) },
(None, false) => match derive_is_bit_valid(ctx, enm, &repr) {
Ok(extra) => extra,
Err(_) if ctx.skip_on_error => return Ok(TokenStream::new()),
Err(e) => return Err(e),
},
};
Ok(ImplBlockBuilder::new(ctx, enm, Trait::TryFromBytes, FieldBounds::ALL_SELF)
.inner_extras(extra)
.build())
}
fn try_gen_trivial_is_bit_valid(ctx: &Ctx, 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 `ctx.skip_on_error` is true, we can't rely on the `FromBytes` derive
// to fail compilation if `Self` is not actually soundly `FromBytes`.
if matches!(top_level, Trait::FromBytes)
&& ctx.ast.generics.params.is_empty()
&& !ctx.skip_on_error
{
let zerocopy_crate = &ctx.zerocopy_crate;
let core = ctx.core_path();
Some(quote!(
// SAFETY: See inline.
#[inline(always)]
fn is_bit_valid<___ZcAlignment>(
_candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
) -> #core::primitive::bool
where
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
{
if false {
fn assert_is_from_bytes<T>()
where
T: #zerocopy_crate::FromBytes,
T: ?#core::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
}
}
/// # Safety
///
/// All initialized bit patterns must be valid for `Self`.
unsafe fn gen_trivial_is_bit_valid_unchecked(ctx: &Ctx) -> proc_macro2::TokenStream {
let zerocopy_crate = &ctx.zerocopy_crate;
let core = ctx.core_path();
quote!(
// SAFETY: The caller of `gen_trivial_is_bit_valid_unchecked` has
// promised that all initialized bit patterns are valid for `Self`.
#[inline(always)]
fn is_bit_valid<___ZcAlignment>(
_candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
) -> #core::primitive::bool
where
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
{
true
}
)
}
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
//
use proc_macro2::{Span, TokenStream};
use syn::{Data, DataEnum, DataStruct, DataUnion, Error};
use crate::{
repr::{EnumRepr, StructUnionRepr},
util::{Ctx, FieldBounds, ImplBlockBuilder, Trait},
};
pub(crate) fn derive_unaligned(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
match &ctx.ast.data {
Data::Struct(strct) => derive_unaligned_struct(ctx, strct),
Data::Enum(enm) => derive_unaligned_enum(ctx, enm),
Data::Union(unn) => derive_unaligned_union(ctx, unn),
}
}
/// 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(ctx: &Ctx, strct: &DataStruct) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&ctx.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 ctx.error_or_skip(Error::new(
Span::call_site(),
"must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment",
));
};
Ok(ImplBlockBuilder::new(ctx, strct, Trait::Unaligned, field_bounds).build())
}
/// An enum is `Unaligned` if:
/// - No `repr(align(N > 1))`
/// - `repr(u8)` or `repr(i8)`
fn derive_unaligned_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
repr.unaligned_validate_no_align_gt_1()?;
if !repr.is_u8() && !repr.is_i8() {
return ctx.error_or_skip(Error::new(
Span::call_site(),
"must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment",
));
}
Ok(ImplBlockBuilder::new(ctx, enm, Trait::Unaligned, FieldBounds::ALL_SELF).build())
}
/// 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(ctx: &Ctx, unn: &DataUnion) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&ctx.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 ctx.error_or_skip(Error::new(
Span::call_site(),
"must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment",
));
};
Ok(ImplBlockBuilder::new(ctx, unn, Trait::Unaligned, field_type_trait_bounds).build())
}