diff --git a/zerocopy-derive/src/derive/from_bytes.rs b/zerocopy-derive/src/derive/from_bytes.rs new file mode 100644 index 0000000000..1f26fa2e25 --- /dev/null +++ b/zerocopy-derive/src/derive/from_bytes.rs @@ -0,0 +1,194 @@ +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 { + // 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::().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::().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 { + 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 { + 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 { + 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 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( + &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 Err(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::>(); + + Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromZeros, FieldBounds::Explicit(explicit_bounds)) + .build()) +} +fn derive_from_zeros_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream { + // FIXME(#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)]); + 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 { + let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?; + + let variants_required = 1usize << enum_size_from_repr(&repr)?; + if enm.variants.len() != variants_required { + return Err(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 { + // FIXME(#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)]); + ImplBlockBuilder::new(ctx, unn, Trait::FromBytes, field_type_trait_bounds).build() +} diff --git a/zerocopy-derive/src/derive/into_bytes.rs b/zerocopy-derive/src/derive/into_bytes.rs new file mode 100644 index 0000000000..a9301ddb36 --- /dev/null +++ b/zerocopy-derive/src/derive/into_bytes.rs @@ -0,0 +1,158 @@ +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 { + 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 { + 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 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(ImplBlockBuilder::new(ctx, strct, Trait::IntoBytes, field_bounds) + .padding_check(padding_check) + .build()) +} + +fn derive_into_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result { + let repr = EnumRepr::from_attrs(&ctx.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 = 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 { + // 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!( + 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 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(&ctx.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 = ImplBlockBuilder::new(ctx, unn, Trait::IntoBytes, FieldBounds::ALL_SELF) + .padding_check(PaddingCheck::Union) + .build(); + Ok(quote!(#cfg_compile_error #impl_block)) +} diff --git a/zerocopy-derive/src/derive/known_layout.rs b/zerocopy-derive/src/derive/known_layout.rs new file mode 100644 index 0000000000..d3cf342fdd --- /dev/null +++ b/zerocopy-derive/src/derive/known_layout.rs @@ -0,0 +1,348 @@ +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)> { + 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, + meta: Self::PointerMetadata, + ) -> #core::ptr::NonNull { + 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::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, _) = 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 { + // 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; + + // 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::(); + + // 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, _meta: ()) -> #core::ptr::NonNull { + bytes.cast::() + } + + #[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() + } + }) +} diff --git a/zerocopy-derive/src/derive/mod.rs b/zerocopy-derive/src/derive/mod.rs new file mode 100644 index 0000000000..7a64f3987d --- /dev/null +++ b/zerocopy-derive/src/derive/mod.rs @@ -0,0 +1,130 @@ +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_no_cell(ctx: &Ctx, _top_level: Trait) -> TokenStream { + 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 { + // 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(&self, state: &mut H) { + #core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(self)) + } + + fn hash_slice(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 { + // 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 { + let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?; + + match &ctx.ast.data { + Data::Struct(_) => {} + Data::Enum(_) | Data::Union(_) => { + return Err(Error::new(Span::call_site(), "can only be applied to structs")); + } + }; + + if repr.get_packed().is_some() { + return Err(Error::new(Span::call_site(), "must not have #[repr(packed)] attribute")); + } + + if !(repr.is_c() || repr.is_transparent()) { + return Err(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 Err(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()) +} diff --git a/zerocopy-derive/src/enum.rs b/zerocopy-derive/src/derive/try_from_bytes.rs similarity index 54% rename from zerocopy-derive/src/enum.rs rename to zerocopy-derive/src/derive/try_from_bytes.rs index 89640eee98..ff67cd91fb 100644 --- a/zerocopy-derive/src/enum.rs +++ b/zerocopy-derive/src/derive/try_from_bytes.rs @@ -1,60 +1,17 @@ -// Copyright 2024 The Fuchsia Authors -// -// Licensed under a BSD-style license , Apache License, Version 2.0 -// , or the MIT -// license , at your option. -// This file may not be copied, modified, or distributed except according to -// those terms. - use proc_macro2::TokenStream; use quote::quote; use syn::{ - parse_quote, spanned::Spanned as _, DataEnum, DeriveInput, Error, Expr, ExprLit, ExprUnary, - Fields, Ident, Index, Lit, UnOp, + parse_quote, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, + Expr, Fields, Ident, Index, Type, }; use crate::{ - derive_has_field_struct_union, derive_try_from_bytes_inner, repr::EnumRepr, Ctx, DataExt, - FieldBounds, ImplBlockBuilder, Trait, + repr::{EnumRepr, StructUnionRepr}, + util::{ + const_block, enum_size_from_repr, generate_tag_enum, Ctx, DataExt, FieldBounds, + ImplBlockBuilder, Trait, TraitBound, + }, }; - -/// Generates a tag enum for the given enum. This generates an enum with the -/// same non-align `repr`s, variants, and corresponding discriminants, but none -/// of the fields. -pub(crate) fn generate_tag_enum(ctx: &Ctx, repr: &EnumRepr, data: &DataEnum) -> TokenStream { - let zerocopy_crate = &ctx.zerocopy_crate; - let variants = data.variants.iter().map(|v| { - let ident = &v.ident; - if let Some((eq, discriminant)) = &v.discriminant { - quote! { #ident #eq #discriminant } - } else { - quote! { #ident } - } - }); - - // Don't include any `repr(align)` when generating the tag enum, as that - // could add padding after the tag but before any variants, which is not the - // correct behavior. - let repr = match repr { - EnumRepr::Transparent(span) => quote::quote_spanned! { *span => #[repr(transparent)] }, - EnumRepr::Compound(c, _) => quote! { #c }, - }; - - quote! { - #repr - #[allow(dead_code)] - enum ___ZerocopyTag { - #(#variants,)* - } - - // SAFETY: `___ZerocopyTag` has no fields, and so it does not permit - // interior mutation. - unsafe impl #zerocopy_crate::Immutable for ___ZerocopyTag { - fn only_derive_is_allowed_to_implement_this_trait() {} - } - } -} - fn tag_ident(variant_ident: &Ident) -> Ident { ident!(("___ZEROCOPY_TAG_{}", variant_ident), variant_ident.span()) } @@ -151,8 +108,8 @@ fn generate_variant_structs(ctx: &Ctx, data: &DataEnum) -> TokenStream { // 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_inner(&ctx.with_input(&variant_struct), Trait::TryFromBytes) - .expect("derive_try_from_bytes_inner should not fail on synthesized type"); + 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 @@ -442,81 +399,293 @@ pub(crate) fn derive_is_bit_valid( } }) } +pub(crate) fn derive_try_from_bytes(ctx: &Ctx, top_level: Trait) -> Result { + 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! {}; + } -/// 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 { - // 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 => (), - } + let field_tokens = fields.iter().map(|(vis, ident, _)| { + let ident = ident!(("ẕ{}", ident), ident.span()); + quote!( + #vis enum #ident {} + ) + }); + + let zerocopy_crate = &ctx.zerocopy_crate; + let variant_id: Box = match &ctx.ast.data { + Data::Struct(_) => parse_quote!({ #zerocopy_crate::STRUCT_VARIANT_ID }), + Data::Union(_) => parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID }), + _ => unreachable!(), + }; + + let is_repr_c_union = match &ctx.ast.data { + Data::Union(..) => { + StructUnionRepr::from_attrs(&ctx.ast.attrs).map(|repr| repr.is_c()).unwrap_or(false) + } + Data::Enum(..) | Data::Struct(..) => false, + }; + let core = ctx.core_path(); + let has_fields = fields.iter().map(move |(_, ident, ty)| { + let field_token = ident!(("ẕ{}", ident), ident.span()); + let field: Box = parse_quote!(#field_token); + let field_id: Box = parse_quote!({ #zerocopy_crate::ident_id!(#ident) }); + ImplBlockBuilder::new( + ctx, + data, + Trait::HasField { + variant_id: variant_id.clone(), + field: field.clone(), + field_id: field_id.clone(), + }, + FieldBounds::None, + ) + .inner_extras(quote! { + type Type = #ty; + + #[inline(always)] + fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut Self::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) } } - // Explicit positive discriminant - Some((_, Expr::Lit(ExprLit { lit: Lit::Int(int), .. }))) => { - match int.base10_parse::().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; - } + }).outer_extras(if is_repr_c_union { + let ident = &ctx.ast.ident; + let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl(); + quote! { + // SAFETY: All `repr(C)` union fields exist at offset 0 within + // the union [1], and so any union projection is actually a cast + // (ie, preserves address). + // + // [1] Per + // https://doc.rust-lang.org/1.92.0/reference/type-layout.html#reprc-unions, + // it's not *technically* guaranteed that non-maximally- + // sized fields are at offset 0, but it's clear that this is + // the intention of `repr(C)` unions. It says: + // + // > A union declared with `#[repr(C)]` will have the same + // > size and alignment as an equivalent C union declaration + // > in the C language for the target platform. + // + // Note that this only mentions size and alignment, not layout. + // However, C unions *do* guarantee that all fields start at + // offset 0. [2] + // + // This is also reinforced by + // https://doc.rust-lang.org/1.92.0/reference/items/unions.html#r-items.union.fields.offset: + // + // > Fields might have a non-zero offset (except when the C + // > representation is used); in that case the bits starting + // > at the offset of the fields are read + // + // [2] Per https://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p16: + // + // > The size of a union is sufficient to contain the + // > largest of its members. The value of at most one of the + // > members can be stored in a union object at any time. A + // > pointer to a union object, suitably converted, points + // > to each of its members (or if a member is a bit- field, + // > then to the unit in which it resides), and vice versa. + // + // FIXME(https://github.com/rust-lang/unsafe-code-guidelines/issues/595): + // Cite the documentation once it's updated. + unsafe impl #impl_generics #zerocopy_crate::pointer::cast::Cast<#ident #ty_generics, #ty> + for #zerocopy_crate::pointer::cast::Projection<#field, { #zerocopy_crate::UNION_VARIANT_ID }, #field_id> + #where_clause + { } } - // Explicit negative discriminant - Some((_, Expr::Unary(ExprUnary { op: UnOp::Neg(_), expr, .. }))) => match &**expr { - Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) => { - match int.base10_parse::().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; - } - } + } else { + quote! {} + }) + .build() + }); + + const_block(field_tokens.into_iter().chain(has_fields).map(Some)) +} +fn derive_try_from_bytes_struct( + ctx: &Ctx, + strct: &DataStruct, + top_level: Trait, +) -> Result { + 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`. + fn is_bit_valid<___ZerocopyAliasing>( + mut candidate: #zerocopy_crate::Maybe, + ) -> #core::primitive::bool + where + ___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference, + { + true #(&& { + let field_candidate = candidate.reborrow().project::< + _, + { #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 { + // FIXME(#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(ctx, top_level).unwrap_or_else(|| { + let zerocopy_crate = &ctx.zerocopy_crate; + 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. + fn is_bit_valid<___ZerocopyAliasing>( + mut candidate: #zerocopy_crate::Maybe<'_, Self,___ZerocopyAliasing> + ) -> #core::primitive::bool + where + ___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference, + { + false #(|| { + // SAFETY: + // - Since `Self: Immutable` is enforced by + // `self_type_trait_bounds`, neither `*slf` nor the + // returned pointer's referent contain any + // `UnsafeCell`s + // - 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<_, { #zerocopy_crate::UNION_VARIANT_ID }, { #zerocopy_crate::ident_id!(#field_names) }> + >() + }; + + <#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate) + })* } - // Unknown negative discriminant (e.g. const repr) - _ => { - has_unknown_discriminants = true; - next_negative_discriminant = None; + ) + }); + 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 { + 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) => derive_is_bit_valid(ctx, enm, &repr)?, + }; + + 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 { + // 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 matches!(top_level, Trait::FromBytes) && ctx.ast.generics.params.is_empty() { + let zerocopy_crate = &ctx.zerocopy_crate; + let core = ctx.core_path(); + Some(quote!( + // SAFETY: See inline. + fn is_bit_valid<___ZerocopyAliasing>( + _candidate: #zerocopy_crate::Maybe, + ) -> #core::primitive::bool + where + ___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference, + { + if false { + fn assert_is_from_bytes() + where + T: #zerocopy_crate::FromBytes, + T: ?#core::marker::Sized, + { + } + + assert_is_from_bytes::(); } - }, - // Unknown discriminant (e.g. const expr) - _ => { - has_unknown_discriminants = true; - next_negative_discriminant = None; + + // 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 } - - Err(has_unknown_discriminants) +} +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`. + fn is_bit_valid<___ZerocopyAliasing>( + _candidate: #zerocopy_crate::Maybe, + ) -> #core::primitive::bool + where + ___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference, + { + true + } + ) } diff --git a/zerocopy-derive/src/derive/unaligned.rs b/zerocopy-derive/src/derive/unaligned.rs new file mode 100644 index 0000000000..b329e860fa --- /dev/null +++ b/zerocopy-derive/src/derive/unaligned.rs @@ -0,0 +1,78 @@ +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 { + 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 { + 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 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(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 { + let repr = EnumRepr::from_attrs(&ctx.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(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 { + 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 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(ImplBlockBuilder::new(ctx, unn, Trait::Unaligned, field_type_trait_bounds).build()) +} diff --git a/zerocopy-derive/src/lib.rs b/zerocopy-derive/src/lib.rs index 315f948549..d6971d3a14 100644 --- a/zerocopy-derive/src/lib.rs +++ b/zerocopy-derive/src/lib.rs @@ -43,20 +43,15 @@ macro_rules! ident { }; } -mod r#enum; +mod derive; #[cfg(test)] mod output_tests; mod repr; mod util; -use proc_macro2::{Span, TokenStream}; -use quote::{quote, ToTokens}; -use syn::{ - parse_quote, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, - Expr, GenericParam, Ident, Path, Type, WherePredicate, -}; +use syn::{DeriveInput, Error}; -use crate::{repr::*, util::*}; +use crate::util::*; // FIXME(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: @@ -82,7 +77,7 @@ use crate::{repr::*, util::*}; /// 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) => { + ($trait:ident => $outer:ident => $inner:path) => { #[proc_macro_derive($trait, attributes(zerocopy))] pub fn $outer(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse_macro_input!(ts as DeriveInput); @@ -100,17 +95,17 @@ macro_rules! derive { } trait IntoTokenStream { - fn into_ts(self) -> TokenStream; + fn into_ts(self) -> proc_macro2::TokenStream; } -impl IntoTokenStream for TokenStream { - fn into_ts(self) -> TokenStream { +impl IntoTokenStream for proc_macro2::TokenStream { + fn into_ts(self) -> proc_macro2::TokenStream { self } } -impl IntoTokenStream for Result { - fn into_ts(self) -> TokenStream { +impl IntoTokenStream for Result { + fn into_ts(self) -> proc_macro2::TokenStream { match self { Ok(ts) => ts, Err(err) => err.to_compile_error(), @@ -118,16 +113,16 @@ impl IntoTokenStream for Result { } } -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); -derive!(SplitAt => derive_split_at => derive_split_at_inner); +derive!(KnownLayout => derive_known_layout => crate::derive::known_layout::derive); +derive!(Immutable => derive_no_cell => crate::derive::derive_no_cell); +derive!(TryFromBytes => derive_try_from_bytes => crate::derive::try_from_bytes::derive_try_from_bytes); +derive!(FromZeros => derive_from_zeros => crate::derive::from_bytes::derive_from_zeros); +derive!(FromBytes => derive_from_bytes => crate::derive::from_bytes::derive_from_bytes); +derive!(IntoBytes => derive_into_bytes => crate::derive::into_bytes::derive_into_bytes); +derive!(Unaligned => derive_unaligned => crate::derive::unaligned::derive_unaligned); +derive!(ByteHash => derive_hash => crate::derive::derive_hash); +derive!(ByteEq => derive_eq => crate::derive::derive_eq); +derive!(SplitAt => derive_split_at => crate::derive::derive_split_at); /// Deprecated: prefer [`FromZeros`] instead. #[deprecated(since = "0.8.0", note = "`FromZeroes` was renamed to `FromZeros`")] @@ -144,1611 +139,3 @@ pub fn derive_from_zeroes(ts: proc_macro::TokenStream) -> proc_macro::TokenStrea pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { derive_into_bytes(ts) } - -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)> { - 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, - meta: Self::PointerMetadata, - ) -> #core::ptr::NonNull { - 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::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, _) = 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))) -} - -fn derive_known_layout_inner(ctx: &Ctx, _top_level: Trait) -> Result { - // 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; - - // 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::(); - - // 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, _meta: ()) -> #core::ptr::NonNull { - bytes.cast::() - } - - #[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() - } - }) -} - -fn derive_no_cell_inner(ctx: &Ctx, _top_level: Trait) -> TokenStream { - 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() - } - } -} - -fn derive_try_from_bytes_inner(ctx: &Ctx, top_level: Trait) -> Result { - 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_from_zeros_inner(ctx: &Ctx, top_level: Trait) -> Result { - let try_from_bytes = derive_try_from_bytes_inner(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()) -} - -fn derive_from_bytes_inner(ctx: &Ctx, top_level: Trait) -> Result { - let from_zeros = derive_from_zeros_inner(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_into_bytes_inner(ctx: &Ctx, _top_level: Trait) -> Result { - 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_unaligned_inner(ctx: &Ctx, _top_level: Trait) -> Result { - 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), - } -} - -fn derive_hash_inner(ctx: &Ctx, _top_level: Trait) -> Result { - // 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(&self, state: &mut H) { - #core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(self)) - } - - fn hash_slice(data: &[Self], state: &mut H) { - #core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(data)) - } - } - }) -} - -fn derive_eq_inner(ctx: &Ctx, _top_level: Trait) -> Result { - // 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 - { - } - }) -} - -fn derive_split_at_inner(ctx: &Ctx, _top_level: Trait) -> Result { - let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?; - - match &ctx.ast.data { - Data::Struct(_) => {} - Data::Enum(_) | Data::Union(_) => { - return Err(Error::new(Span::call_site(), "can only be applied to structs")); - } - }; - - if repr.get_packed().is_some() { - return Err(Error::new(Span::call_site(), "must not have #[repr(packed)] attribute")); - } - - if !(repr.is_c() || repr.is_transparent()) { - return Err(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 Err(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()) -} - -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!(("ẕ{}", ident), ident.span()); - quote!( - #vis enum #ident {} - ) - }); - - let zerocopy_crate = &ctx.zerocopy_crate; - let variant_id: Box = match &ctx.ast.data { - Data::Struct(_) => parse_quote!({ #zerocopy_crate::STRUCT_VARIANT_ID }), - Data::Union(_) => parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID }), - _ => unreachable!(), - }; - - let is_repr_c_union = match &ctx.ast.data { - Data::Union(..) => { - StructUnionRepr::from_attrs(&ctx.ast.attrs).map(|repr| repr.is_c()).unwrap_or(false) - } - Data::Enum(..) | Data::Struct(..) => false, - }; - let core = ctx.core_path(); - let has_fields = fields.iter().map(move |(_, ident, ty)| { - let field_token = ident!(("ẕ{}", ident), ident.span()); - let field: Box = parse_quote!(#field_token); - let field_id: Box = parse_quote!({ #zerocopy_crate::ident_id!(#ident) }); - ImplBlockBuilder::new( - ctx, - data, - Trait::HasField { - variant_id: variant_id.clone(), - field: field.clone(), - field_id: field_id.clone(), - }, - FieldBounds::None, - ) - .inner_extras(quote! { - type Type = #ty; - - #[inline(always)] - fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut Self::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 is_repr_c_union { - let ident = &ctx.ast.ident; - let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl(); - quote! { - // SAFETY: All `repr(C)` union fields exist at offset 0 within - // the union [1], and so any union projection is actually a cast - // (ie, preserves address). - // - // [1] Per - // https://doc.rust-lang.org/1.92.0/reference/type-layout.html#reprc-unions, - // it's not *technically* guaranteed that non-maximally- - // sized fields are at offset 0, but it's clear that this is - // the intention of `repr(C)` unions. It says: - // - // > A union declared with `#[repr(C)]` will have the same - // > size and alignment as an equivalent C union declaration - // > in the C language for the target platform. - // - // Note that this only mentions size and alignment, not layout. - // However, C unions *do* guarantee that all fields start at - // offset 0. [2] - // - // This is also reinforced by - // https://doc.rust-lang.org/1.92.0/reference/items/unions.html#r-items.union.fields.offset: - // - // > Fields might have a non-zero offset (except when the C - // > representation is used); in that case the bits starting - // > at the offset of the fields are read - // - // [2] Per https://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p16: - // - // > The size of a union is sufficient to contain the - // > largest of its members. The value of at most one of the - // > members can be stored in a union object at any time. A - // > pointer to a union object, suitably converted, points - // > to each of its members (or if a member is a bit- field, - // > then to the unit in which it resides), and vice versa. - // - // FIXME(https://github.com/rust-lang/unsafe-code-guidelines/issues/595): - // Cite the documentation once it's updated. - unsafe impl #impl_generics #zerocopy_crate::pointer::cast::Cast<#ident #ty_generics, #ty> - for #zerocopy_crate::pointer::cast::Projection<#field, { #zerocopy_crate::UNION_VARIANT_ID }, #field_id> - #where_clause - { - } - } - } else { - quote! {} - }) - .build() - }); - - const_block(field_tokens.into_iter().chain(has_fields).map(Some)) -} - -/// A struct is `TryFromBytes` if: -/// - all fields are `TryFromBytes` -fn derive_try_from_bytes_struct( - ctx: &Ctx, - strct: &DataStruct, - top_level: Trait, -) -> Result { - 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`. - fn is_bit_valid<___ZerocopyAliasing>( - mut candidate: #zerocopy_crate::Maybe, - ) -> #core::primitive::bool - where - ___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference, - { - true #(&& { - let field_candidate = candidate.reborrow().project::< - _, - { #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()) -} - -/// A union is `TryFromBytes` if: -/// - all of its fields are `TryFromBytes` and `Immutable` -fn derive_try_from_bytes_union(ctx: &Ctx, unn: &DataUnion, top_level: Trait) -> TokenStream { - // FIXME(#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(ctx, top_level).unwrap_or_else(|| { - let zerocopy_crate = &ctx.zerocopy_crate; - 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. - fn is_bit_valid<___ZerocopyAliasing>( - mut candidate: #zerocopy_crate::Maybe<'_, Self,___ZerocopyAliasing> - ) -> #core::primitive::bool - where - ___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference, - { - false #(|| { - // SAFETY: - // - Since `Self: Immutable` is enforced by - // `self_type_trait_bounds`, neither `*slf` nor the - // returned pointer's referent contain any - // `UnsafeCell`s - // - 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<_, { #zerocopy_crate::UNION_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 { - 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) => r#enum::derive_is_bit_valid(ctx, enm, &repr)?, - }; - - Ok(ImplBlockBuilder::new(ctx, enm, Trait::TryFromBytes, FieldBounds::ALL_SELF) - .inner_extras(extra) - .build()) -} - -/// 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` ought to be `TryFromBytes` but not `FromBytes` -/// in this example: -/// -/// ```rust,ignore -/// #[derive(FromBytes)] -/// #[repr(transparent)] -/// struct Foo(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(ctx: &Ctx, top_level: Trait) -> Option { - // 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 matches!(top_level, Trait::FromBytes) && ctx.ast.generics.params.is_empty() { - let zerocopy_crate = &ctx.zerocopy_crate; - let core = ctx.core_path(); - Some(quote!( - // SAFETY: See inline. - fn is_bit_valid<___ZerocopyAliasing>( - _candidate: #zerocopy_crate::Maybe, - ) -> #core::primitive::bool - where - ___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference, - { - if false { - fn assert_is_from_bytes() - where - T: #zerocopy_crate::FromBytes, - T: ?#core::marker::Sized, - { - } - - assert_is_from_bytes::(); - } - - // 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(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`. - fn is_bit_valid<___ZerocopyAliasing>( - _candidate: #zerocopy_crate::Maybe, - ) -> #core::primitive::bool - where - ___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference, - { - true - } - ) -} - -/// A struct is `FromZeros` if: -/// - all fields are `FromZeros` -fn derive_from_zeros_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream { - ImplBlockBuilder::new(ctx, strct, Trait::FromZeros, FieldBounds::ALL_SELF).build() -} - -/// 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(ctx: &Ctx, enm: &DataEnum) -> Result { - 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 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 r#enum::find_zero_variant(enm) { - Ok(index) => enm.variants.iter().nth(index).unwrap(), - // Has unknown variants - Err(true) => { - return Err(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 Err(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::>(); - - Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromZeros, FieldBounds::Explicit(explicit_bounds)) - .build()) -} - -/// Unions are `FromZeros` if -/// - all fields are `FromZeros` and `Immutable` -fn derive_from_zeros_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream { - // FIXME(#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)]); - ImplBlockBuilder::new(ctx, unn, Trait::FromZeros, field_type_trait_bounds).build() -} - -/// A struct is `FromBytes` if: -/// - all fields are `FromBytes` -fn derive_from_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream { - ImplBlockBuilder::new(ctx, strct, Trait::FromBytes, FieldBounds::ALL_SELF).build() -} - -/// 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(ctx: &Ctx, enm: &DataEnum) -> Result { - let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?; - - let variants_required = 1usize << enum_size_from_repr(&repr)?; - if enm.variants.len() != variants_required { - return Err(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()) -} - -// Returns `None` if the enum's size is not guaranteed by the repr. -fn enum_size_from_repr(repr: &EnumRepr) -> Result { - use CompoundRepr::*; - use PrimitiveRepr::*; - use Repr::*; - match repr { - Transparent(span) - | Compound( - Spanned { - t: C | Rust | Primitive(U32 | I32 | U64 | I64 | U128 | I128 | 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(ctx: &Ctx, unn: &DataUnion) -> TokenStream { - // FIXME(#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)]); - ImplBlockBuilder::new(ctx, unn, Trait::FromBytes, field_type_trait_bounds).build() -} - -fn derive_into_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> Result { - 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 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(ImplBlockBuilder::new(ctx, strct, Trait::IntoBytes, field_bounds) - .padding_check(padding_check) - .build()) -} - -/// 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(ctx: &Ctx, enm: &DataEnum) -> Result { - let repr = EnumRepr::from_attrs(&ctx.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(ctx, &repr, enm); - Ok(ImplBlockBuilder::new(ctx, enm, Trait::IntoBytes, FieldBounds::ALL_SELF) - .padding_check(PaddingCheck::Enum { tag_type_definition }) - .build()) -} - -/// 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(ctx: &Ctx, unn: &DataUnion) -> Result { - // 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!( - 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 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(&ctx.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 = ImplBlockBuilder::new(ctx, unn, Trait::IntoBytes, FieldBounds::ALL_SELF) - .padding_check(PaddingCheck::Union) - .build(); - 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(ctx: &Ctx, strct: &DataStruct) -> Result { - 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 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(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 { - let repr = EnumRepr::from_attrs(&ctx.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(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 { - 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 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(ImplBlockBuilder::new(ctx, unn, Trait::Unaligned, field_type_trait_bounds).build()) -} - -/// 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 a `repr(C)` struct has no padding. - ReprCStruct, - /// 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 idents of the trait to use and the macro to call in order to - /// validate that a type passes the relevant padding check. - fn validator_trait_and_macro_idents(&self) -> (Ident, Ident) { - let (trt, mcro) = match self { - PaddingCheck::Struct => ("PaddingFree", "struct_padding"), - PaddingCheck::ReprCStruct => ("DynamicPaddingFree", "repr_c_struct_has_padding"), - PaddingCheck::Union => ("PaddingFree", "union_padding"), - PaddingCheck::Enum { .. } => ("PaddingFree", "enum_padding"), - }; - - let trt = Ident::new(trt, Span::call_site()); - let mcro = Ident::new(mcro, Span::call_site()); - (trt, mcro) - } - - /// 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::ReprCStruct | PaddingCheck::Union => None, - PaddingCheck::Enum { tag_type_definition } => Some(tag_type_definition), - } - } -} - -#[derive(Clone)] -enum Trait { - KnownLayout, - HasField { variant_id: Box, field: Box, field_id: Box }, - Immutable, - TryFromBytes, - FromZeros, - FromBytes, - IntoBytes, - Unaligned, - Sized, - ByteHash, - ByteEq, - SplitAt, -} - -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 independent 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::HasField { .. } => "HasField", - 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", - Trait::SplitAt => "SplitAt", - }; - let ident = Ident::new(s, Span::call_site()); - let arguments: Option = match self { - Trait::HasField { variant_id, field, field_id } => { - Some(parse_quote!(<#field, #variant_id, #field_id>)) - } - Trait::KnownLayout - | Trait::Immutable - | Trait::TryFromBytes - | Trait::FromZeros - | Trait::FromBytes - | Trait::IntoBytes - | Trait::Unaligned - | Trait::Sized - | Trait::ByteHash - | Trait::ByteEq - | Trait::SplitAt => None, - }; - tokens.extend(quote!(#ident #arguments)); - } -} - -impl Trait { - fn crate_path(&self, ctx: &Ctx) -> Path { - let zerocopy_crate = &ctx.zerocopy_crate; - let core = ctx.core_path(); - match self { - Self::Sized => parse_quote!(#core::marker::#self), - _ => parse_quote!(#zerocopy_crate::#self), - } - } -} - -enum TraitBound { - Slf, - Other(Trait), -} - -enum FieldBounds<'a> { - None, - All(&'a [TraitBound]), - Trailing(&'a [TraitBound]), - Explicit(Vec), -} - -impl<'a> FieldBounds<'a> { - const ALL_SELF: FieldBounds<'a> = FieldBounds::All(&[TraitBound::Slf]); - const TRAILING_SELF: FieldBounds<'a> = FieldBounds::Trailing(&[TraitBound::Slf]); -} - -enum SelfBounds<'a> { - None, - All(&'a [Trait]), -} - -// FIXME(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<'a>( - slf: &'a Trait, - bounds: &'a [TraitBound], -) -> impl 'a + Iterator { - bounds.iter().map(move |bound| match bound { - TraitBound::Slf => slf.clone(), - TraitBound::Other(trt) => trt.clone(), - }) -} - -struct ImplBlockBuilder<'a> { - ctx: &'a Ctx, - data: &'a dyn DataExt, - trt: Trait, - field_type_trait_bounds: FieldBounds<'a>, - self_type_trait_bounds: SelfBounds<'a>, - padding_check: Option, - inner_extras: Option, - outer_extras: Option, -} - -impl<'a> ImplBlockBuilder<'a> { - fn new( - ctx: &'a Ctx, - data: &'a dyn DataExt, - trt: Trait, - field_type_trait_bounds: FieldBounds<'a>, - ) -> Self { - Self { - ctx, - data, - trt, - field_type_trait_bounds, - self_type_trait_bounds: SelfBounds::None, - padding_check: None, - inner_extras: None, - outer_extras: None, - } - } - - fn self_type_trait_bounds(mut self, self_type_trait_bounds: SelfBounds<'a>) -> Self { - self.self_type_trait_bounds = self_type_trait_bounds; - self - } - - fn padding_check>>(mut self, padding_check: P) -> Self { - self.padding_check = padding_check.into(); - self - } - - fn inner_extras(mut self, inner_extras: TokenStream) -> Self { - self.inner_extras = Some(inner_extras); - self - } - - fn outer_extras>>(mut self, outer_extras: T) -> Self { - self.outer_extras = outer_extras.into(); - self - } - - fn build(self) -> TokenStream { - // In this documentation, we will refer to this hypothetical struct: - // - // #[derive(FromBytes)] - // struct Foo - // 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 FromBytes for Foo - // 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 = &self.ctx.ast.ident; - let trait_path = self.trt.crate_path(self.ctx); - let fields = self.data.fields(); - let variants = self.data.variants(); - let tag = self.data.tag(); - let zerocopy_crate = &self.ctx.zerocopy_crate; - - fn bound_tt(ty: &Type, traits: impl Iterator, ctx: &Ctx) -> WherePredicate { - let traits = traits.map(|t| t.crate_path(ctx)); - parse_quote!(#ty: #(#traits)+*) - } - let field_type_bounds: Vec<_> = match (self.field_type_trait_bounds, &fields[..]) { - (FieldBounds::All(traits), _) => fields - .iter() - .map(|(_vis, _name, ty)| { - bound_tt(ty, normalize_bounds(&self.trt, traits), self.ctx) - }) - .collect(), - (FieldBounds::None, _) | (FieldBounds::Trailing(..), []) => vec![], - (FieldBounds::Trailing(traits), [.., last]) => { - vec![bound_tt(last.2, normalize_bounds(&self.trt, traits), self.ctx)] - } - (FieldBounds::Explicit(bounds), _) => bounds, - }; - - // Don't bother emitting a padding check if there are no fields. - #[allow(unstable_name_collisions)] // See `BoolExt` below - let padding_check_bound = self - .padding_check - .and_then(|check| (!fields.is_empty()).then_some(check)) - .map(|check| { - let variant_types = variants.iter().map(|(_, fields)| { - let types = fields.iter().map(|(_vis, _name, ty)| ty); - quote!([#((#types)),*]) - }); - let validator_context = check.validator_macro_context(); - let (trt, validator_macro) = check.validator_trait_and_macro_idents(); - let t = tag.iter(); - parse_quote! { - (): #zerocopy_crate::util::macro_util::#trt< - Self, - { - #validator_context - #zerocopy_crate::#validator_macro!(Self, #(#t,)* #(#variant_types),*) - } - > - } - }); - - let self_bounds: Option = match self.self_type_trait_bounds { - SelfBounds::None => None, - SelfBounds::All(traits) => { - Some(bound_tt(&parse_quote!(Self), traits.iter().cloned(), self.ctx)) - } - }; - - let bounds = self - .ctx - .ast - .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 = self.ctx.ast.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 = self.ctx.ast.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 inner_extras = self.inner_extras; - let impl_tokens = quote! { - unsafe impl < #(#params),* > #trait_path for #type_ident < #(#param_idents),* > - where - #(#bounds,)* - { - fn only_derive_is_allowed_to_implement_this_trait() {} - - #inner_extras - } - }; - - let outer_extras = self.outer_extras.filter(|e| !e.is_empty()); - const_block([Some(impl_tokens), outer_extras]) - } -} - -// 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. -// -// FIXME(#67): Remove this once our MSRV is >= 1.62. -#[allow(unused)] -trait BoolExt { - fn then_some(self, t: T) -> Option; -} - -impl BoolExt for bool { - fn then_some(self, t: T) -> Option { - if self { - Some(t) - } else { - None - } - } -} - -fn const_block(items: impl IntoIterator>) -> TokenStream { - let items = items.into_iter().flatten(); - quote! { - #[allow( - // FIXME(#553): Add a test that generates a warning when - // `#[allow(deprecated)]` isn't present. - deprecated, - // 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. - private_bounds, - non_local_definitions, - non_camel_case_types, - non_upper_case_globals, - non_snake_case, - non_ascii_idents, - clippy::missing_inline_in_public_items, - )] - // While there are not currently any warnings that this suppresses - // (that we're aware of), it's good future-proofing hygiene. - #[automatically_derived] - const _: () = { - #(#items)* - }; - } -} diff --git a/zerocopy-derive/src/output_tests.rs b/zerocopy-derive/src/output_tests.rs index 1a2d14fee2..c28e692be4 100644 --- a/zerocopy-derive/src/output_tests.rs +++ b/zerocopy-derive/src/output_tests.rs @@ -12,24 +12,24 @@ use proc_macro2::TokenStream; use crate::IntoTokenStream; macro_rules! use_as_trait_name { - ($($alias:ident => $derive:ident),* $(,)?) => { - $(use super::$derive as $alias;)* + ($($alias:ident => $derive:path),* $(,)?) => { + $(use $derive as $alias;)* }; } // This permits invocations of `test!` to be more ergonomic, passing the name of // the trait under test rather than the name of the inner derive function. use_as_trait_name!( - KnownLayout => derive_known_layout_inner, - Immutable => derive_no_cell_inner, - TryFromBytes => derive_try_from_bytes_inner, - FromZeros => derive_from_zeros_inner, - FromBytes => derive_from_bytes_inner, - IntoBytes => derive_into_bytes_inner, - Unaligned => derive_unaligned_inner, - ByteHash => derive_hash_inner, - ByteEq => derive_eq_inner, - SplitAt => derive_split_at_inner, + KnownLayout => super::derive::known_layout::derive, + Immutable => super::derive::derive_no_cell, + TryFromBytes => super::derive::try_from_bytes::derive_try_from_bytes, + FromZeros => super::derive::from_bytes::derive_from_zeros, + FromBytes => super::derive::from_bytes::derive_from_bytes, + IntoBytes => super::derive::into_bytes::derive_into_bytes, + Unaligned => super::derive::unaligned::derive_unaligned, + ByteHash => super::derive::derive_hash, + ByteEq => super::derive::derive_eq, + SplitAt => super::derive::derive_split_at, ); /// Test that the given derive input expands to the expected output. @@ -56,7 +56,7 @@ macro_rules! test { let ts: proc_macro2::TokenStream = quote::quote!( $($i)* ); let ast = syn::parse2::(ts).unwrap(); let ctx = crate::Ctx::try_from_derive_input(ast).unwrap(); - let res = $name(&ctx, crate::Trait::$name); + let res = $name(&ctx, crate::util::Trait::$name); let expected_toks = quote::quote!( $($o)* ); assert_eq_streams(expected_toks.into(), res.into_ts().into()); } diff --git a/zerocopy-derive/src/util.rs b/zerocopy-derive/src/util.rs index 5803f7702a..93c5d30ff7 100644 --- a/zerocopy-derive/src/util.rs +++ b/zerocopy-derive/src/util.rs @@ -10,9 +10,11 @@ use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ parse_quote, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, Expr, ExprLit, Field, - Ident, Index, Lit, Meta, Path, Type, Variant, Visibility, + GenericParam, Ident, Index, Lit, Meta, Path, Type, Variant, Visibility, WherePredicate, }; +use crate::repr::{CompoundRepr, EnumRepr, PrimitiveRepr, Repr, Spanned}; + pub(crate) struct Ctx { pub(crate) ast: DeriveInput, pub(crate) zerocopy_crate: Path, @@ -179,3 +181,484 @@ pub(crate) fn to_ident_str(t: &impl ToString) -> String { s } } + +/// This enum describes what kind of padding check needs to be generated for the +/// associated impl. +pub(crate) enum PaddingCheck { + /// Check that the sum of the fields' sizes exactly equals the struct's + /// size. + Struct, + /// Check that a `repr(C)` struct has no padding. + ReprCStruct, + /// 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 idents of the trait to use and the macro to call in order to + /// validate that a type passes the relevant padding check. + pub(crate) fn validator_trait_and_macro_idents(&self) -> (Ident, Ident) { + let (trt, mcro) = match self { + PaddingCheck::Struct => ("PaddingFree", "struct_padding"), + PaddingCheck::ReprCStruct => ("DynamicPaddingFree", "repr_c_struct_has_padding"), + PaddingCheck::Union => ("PaddingFree", "union_padding"), + PaddingCheck::Enum { .. } => ("PaddingFree", "enum_padding"), + }; + + let trt = Ident::new(trt, Span::call_site()); + let mcro = Ident::new(mcro, Span::call_site()); + (trt, mcro) + } + + /// Sometimes performing the padding check requires some additional + /// "context" code. For enums, this is the definition of the tag enum. + pub(crate) fn validator_macro_context(&self) -> Option<&TokenStream> { + match self { + PaddingCheck::Struct | PaddingCheck::ReprCStruct | PaddingCheck::Union => None, + PaddingCheck::Enum { tag_type_definition } => Some(tag_type_definition), + } + } +} + +#[derive(Clone)] +pub(crate) enum Trait { + KnownLayout, + HasField { variant_id: Box, field: Box, field_id: Box }, + Immutable, + TryFromBytes, + FromZeros, + FromBytes, + IntoBytes, + Unaligned, + Sized, + ByteHash, + ByteEq, + SplitAt, +} + +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 independent 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::HasField { .. } => "HasField", + 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", + Trait::SplitAt => "SplitAt", + }; + let ident = Ident::new(s, Span::call_site()); + let arguments: Option = match self { + Trait::HasField { variant_id, field, field_id } => { + Some(parse_quote!(<#field, #variant_id, #field_id>)) + } + Trait::KnownLayout + | Trait::Immutable + | Trait::TryFromBytes + | Trait::FromZeros + | Trait::FromBytes + | Trait::IntoBytes + | Trait::Unaligned + | Trait::Sized + | Trait::ByteHash + | Trait::ByteEq + | Trait::SplitAt => None, + }; + tokens.extend(quote!(#ident #arguments)); + } +} + +impl Trait { + pub(crate) fn crate_path(&self, ctx: &Ctx) -> Path { + let zerocopy_crate = &ctx.zerocopy_crate; + let core = ctx.core_path(); + match self { + Self::Sized => parse_quote!(#core::marker::#self), + _ => parse_quote!(#zerocopy_crate::#self), + } + } +} + +pub(crate) enum TraitBound { + Slf, + Other(Trait), +} + +pub(crate) enum FieldBounds<'a> { + None, + All(&'a [TraitBound]), + Trailing(&'a [TraitBound]), + Explicit(Vec), +} + +impl<'a> FieldBounds<'a> { + pub(crate) const ALL_SELF: FieldBounds<'a> = FieldBounds::All(&[TraitBound::Slf]); + pub(crate) const TRAILING_SELF: FieldBounds<'a> = FieldBounds::Trailing(&[TraitBound::Slf]); +} + +pub(crate) enum SelfBounds<'a> { + None, + All(&'a [Trait]), +} + +// FIXME(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> { + pub(crate) const SIZED: Self = Self::All(&[Trait::Sized]); +} + +/// Normalizes a slice of bounds by replacing [`TraitBound::Slf`] with `slf`. +pub(crate) fn normalize_bounds<'a>( + slf: &'a Trait, + bounds: &'a [TraitBound], +) -> impl 'a + Iterator { + bounds.iter().map(move |bound| match bound { + TraitBound::Slf => slf.clone(), + TraitBound::Other(trt) => trt.clone(), + }) +} + +pub(crate) struct ImplBlockBuilder<'a> { + ctx: &'a Ctx, + data: &'a dyn DataExt, + trt: Trait, + field_type_trait_bounds: FieldBounds<'a>, + self_type_trait_bounds: SelfBounds<'a>, + padding_check: Option, + inner_extras: Option, + outer_extras: Option, +} + +impl<'a> ImplBlockBuilder<'a> { + pub(crate) fn new( + ctx: &'a Ctx, + data: &'a dyn DataExt, + trt: Trait, + field_type_trait_bounds: FieldBounds<'a>, + ) -> Self { + Self { + ctx, + data, + trt, + field_type_trait_bounds, + self_type_trait_bounds: SelfBounds::None, + padding_check: None, + inner_extras: None, + outer_extras: None, + } + } + + pub(crate) fn self_type_trait_bounds(mut self, self_type_trait_bounds: SelfBounds<'a>) -> Self { + self.self_type_trait_bounds = self_type_trait_bounds; + self + } + + pub(crate) fn padding_check>>(mut self, padding_check: P) -> Self { + self.padding_check = padding_check.into(); + self + } + + pub(crate) fn inner_extras(mut self, inner_extras: TokenStream) -> Self { + self.inner_extras = Some(inner_extras); + self + } + + pub(crate) fn outer_extras>>(mut self, outer_extras: T) -> Self { + self.outer_extras = outer_extras.into(); + self + } + + pub(crate) fn build(self) -> TokenStream { + // In this documentation, we will refer to this hypothetical struct: + // + // #[derive(FromBytes)] + // struct Foo + // 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 FromBytes for Foo + // 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 = &self.ctx.ast.ident; + let trait_path = self.trt.crate_path(self.ctx); + let fields = self.data.fields(); + let variants = self.data.variants(); + let tag = self.data.tag(); + let zerocopy_crate = &self.ctx.zerocopy_crate; + + fn bound_tt(ty: &Type, traits: impl Iterator, ctx: &Ctx) -> WherePredicate { + let traits = traits.map(|t| t.crate_path(ctx)); + parse_quote!(#ty: #(#traits)+*) + } + let field_type_bounds: Vec<_> = match (self.field_type_trait_bounds, &fields[..]) { + (FieldBounds::All(traits), _) => fields + .iter() + .map(|(_vis, _name, ty)| { + bound_tt(ty, normalize_bounds(&self.trt, traits), self.ctx) + }) + .collect(), + (FieldBounds::None, _) | (FieldBounds::Trailing(..), []) => vec![], + (FieldBounds::Trailing(traits), [.., last]) => { + vec![bound_tt(last.2, normalize_bounds(&self.trt, traits), self.ctx)] + } + (FieldBounds::Explicit(bounds), _) => bounds, + }; + + // Don't bother emitting a padding check if there are no fields. + #[allow(unstable_name_collisions)] // See `BoolExt` below + let padding_check_bound = self + .padding_check + .and_then(|check| (!fields.is_empty()).then_some(check)) + .map(|check| { + let variant_types = variants.iter().map(|(_, fields)| { + let types = fields.iter().map(|(_vis, _name, ty)| ty); + quote!([#((#types)),*]) + }); + let validator_context = check.validator_macro_context(); + let (trt, validator_macro) = check.validator_trait_and_macro_idents(); + let t = tag.iter(); + parse_quote! { + (): #zerocopy_crate::util::macro_util::#trt< + Self, + { + #validator_context + #zerocopy_crate::#validator_macro!(Self, #(#t,)* #(#variant_types),*) + } + > + } + }); + + let self_bounds: Option = match self.self_type_trait_bounds { + SelfBounds::None => None, + SelfBounds::All(traits) => { + Some(bound_tt(&parse_quote!(Self), traits.iter().cloned(), self.ctx)) + } + }; + + let bounds = self + .ctx + .ast + .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 = self.ctx.ast.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 = self.ctx.ast.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 inner_extras = self.inner_extras; + let impl_tokens = quote! { + unsafe impl < #(#params),* > #trait_path for #type_ident < #(#param_idents),* > + where + #(#bounds,)* + { + fn only_derive_is_allowed_to_implement_this_trait() {} + + #inner_extras + } + }; + + let outer_extras = self.outer_extras.filter(|e| !e.is_empty()); + const_block([Some(impl_tokens), outer_extras]) + } +} + +// 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. +// +// FIXME(#67): Remove this once our MSRV is >= 1.62. +#[allow(unused)] +trait BoolExt { + fn then_some(self, t: T) -> Option; +} + +impl BoolExt for bool { + fn then_some(self, t: T) -> Option { + if self { + Some(t) + } else { + None + } + } +} + +pub(crate) fn const_block(items: impl IntoIterator>) -> TokenStream { + let items = items.into_iter().flatten(); + quote! { + #[allow( + // FIXME(#553): Add a test that generates a warning when + // `#[allow(deprecated)]` isn't present. + deprecated, + // 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. + private_bounds, + non_local_definitions, + non_camel_case_types, + non_upper_case_globals, + non_snake_case, + non_ascii_idents, + clippy::missing_inline_in_public_items, + )] + // While there are not currently any warnings that this suppresses + // (that we're aware of), it's good future-proofing hygiene. + #[automatically_derived] + const _: () = { + #(#items)* + }; + } +} +pub(crate) fn generate_tag_enum(ctx: &Ctx, repr: &EnumRepr, data: &DataEnum) -> TokenStream { + let zerocopy_crate = &ctx.zerocopy_crate; + let variants = data.variants.iter().map(|v| { + let ident = &v.ident; + if let Some((eq, discriminant)) = &v.discriminant { + quote! { #ident #eq #discriminant } + } else { + quote! { #ident } + } + }); + + // Don't include any `repr(align)` when generating the tag enum, as that + // could add padding after the tag but before any variants, which is not the + // correct behavior. + let repr = match repr { + EnumRepr::Transparent(span) => quote::quote_spanned! { *span => #[repr(transparent)] }, + EnumRepr::Compound(c, _) => quote! { #c }, + }; + + quote! { + #repr + #[allow(dead_code)] + enum ___ZerocopyTag { + #(#variants,)* + } + + // SAFETY: `___ZerocopyTag` has no fields, and so it does not permit + // interior mutation. + unsafe impl #zerocopy_crate::Immutable for ___ZerocopyTag { + fn only_derive_is_allowed_to_implement_this_trait() {} + } + } +} +pub(crate) fn enum_size_from_repr(repr: &EnumRepr) -> Result { + use CompoundRepr::*; + use PrimitiveRepr::*; + use Repr::*; + match repr { + Transparent(span) + | Compound( + Spanned { + t: C | Rust | Primitive(U32 | I32 | U64 | I64 | U128 | I128 | 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), + } +}