// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Data, DataEnum, DataStruct, DataUnion, Error, Type}; use crate::{ repr::{EnumRepr, StructUnionRepr}, util::{ generate_tag_enum, Ctx, DataExt, FieldBounds, ImplBlockBuilder, PaddingCheck, Trait, TraitBound, }, }; pub(crate) fn derive_into_bytes(ctx: &Ctx, _top_level: Trait) -> Result { 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 ctx.error_or_skip(Error::new( Span::call_site(), "must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout", )); }; let field_bounds = if require_unaligned_fields { FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Unaligned)]) } else { FieldBounds::ALL_SELF }; Ok(ImplBlockBuilder::new(ctx, strct, Trait::IntoBytes, field_bounds) .padding_check(padding_check) .build()) } fn derive_into_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result { let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?; if !repr.is_c() && !repr.is_primitive() { return ctx.error_or_skip(Error::new( Span::call_site(), "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout", )); } let tag_type_definition = generate_tag_enum(ctx, &repr, enm); Ok(ImplBlockBuilder::new(ctx, enm, Trait::IntoBytes, FieldBounds::ALL_SELF) .padding_check(PaddingCheck::Enum { tag_type_definition }) .build()) } fn derive_into_bytes_union(ctx: &Ctx, unn: &DataUnion) -> Result { // See #1792 for more context. // // By checking for `zerocopy_derive_union_into_bytes` both here and in the // generated code, we ensure that `--cfg zerocopy_derive_union_into_bytes` // need only be passed *either* when compiling this crate *or* when // compiling the user's crate. The former is preferable, but in some // situations (such as when cross-compiling using `cargo build --target`), // it doesn't get propagated to this crate's build by default. let cfg_compile_error = if cfg!(zerocopy_derive_union_into_bytes) { quote!() } else { let core = ctx.core_path(); let error_message = "requires --cfg zerocopy_derive_union_into_bytes; please let us know you use this feature: https://github.com/google/zerocopy/discussions/1802"; quote!( #[allow(unused_attributes, unexpected_cfgs)] const _: () = { #[cfg(not(zerocopy_derive_union_into_bytes))] #core::compile_error!(#error_message); }; ) }; // FIXME(#10): Support type parameters. if !ctx.ast.generics.params.is_empty() { return ctx.error_or_skip(Error::new( Span::call_site(), "unsupported on types with type parameters", )); } // Because we don't support generics, we don't need to worry about // special-casing different reprs. So long as there is *some* repr which // guarantees the layout, our `PaddingCheck::Union` guarantees that there is // no padding. let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?; if !repr.is_c() && !repr.is_transparent() && !repr.is_packed_1() { return ctx.error_or_skip(Error::new( Span::call_site(), "must be #[repr(C)], #[repr(packed)], or #[repr(transparent)]", )); } let impl_block = ImplBlockBuilder::new(ctx, unn, Trait::IntoBytes, FieldBounds::ALL_SELF) .padding_check(PaddingCheck::Union) .build(); Ok(quote!(#cfg_compile_error #impl_block)) }