1*5f85604cSMiguel Ojeda // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT 2*5f85604cSMiguel Ojeda 3b437b383SMiguel Ojeda use proc_macro2::{Span, TokenStream}; 4b437b383SMiguel Ojeda use quote::quote; 5b437b383SMiguel Ojeda use syn::{Data, DataEnum, DataStruct, DataUnion, Error, Type}; 6b437b383SMiguel Ojeda 7b437b383SMiguel Ojeda use crate::{ 8b437b383SMiguel Ojeda repr::{EnumRepr, StructUnionRepr}, 9b437b383SMiguel Ojeda util::{ 10b437b383SMiguel Ojeda generate_tag_enum, Ctx, DataExt, FieldBounds, ImplBlockBuilder, PaddingCheck, Trait, 11b437b383SMiguel Ojeda TraitBound, 12b437b383SMiguel Ojeda }, 13b437b383SMiguel Ojeda }; 14b437b383SMiguel Ojeda pub(crate) fn derive_into_bytes(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> { 15b437b383SMiguel Ojeda match &ctx.ast.data { 16b437b383SMiguel Ojeda Data::Struct(strct) => derive_into_bytes_struct(ctx, strct), 17b437b383SMiguel Ojeda Data::Enum(enm) => derive_into_bytes_enum(ctx, enm), 18b437b383SMiguel Ojeda Data::Union(unn) => derive_into_bytes_union(ctx, unn), 19b437b383SMiguel Ojeda } 20b437b383SMiguel Ojeda } 21b437b383SMiguel Ojeda fn derive_into_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> Result<TokenStream, Error> { 22b437b383SMiguel Ojeda let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?; 23b437b383SMiguel Ojeda 24b437b383SMiguel Ojeda let is_transparent = repr.is_transparent(); 25b437b383SMiguel Ojeda let is_c = repr.is_c(); 26b437b383SMiguel Ojeda let is_packed_1 = repr.is_packed_1(); 27b437b383SMiguel Ojeda let num_fields = strct.fields().len(); 28b437b383SMiguel Ojeda 29b437b383SMiguel Ojeda let (padding_check, require_unaligned_fields) = if is_transparent || is_packed_1 { 30b437b383SMiguel Ojeda // No padding check needed. 31b437b383SMiguel Ojeda // - repr(transparent): The layout and ABI of the whole struct is the 32b437b383SMiguel Ojeda // same as its only non-ZST field (meaning there's no padding outside 33b437b383SMiguel Ojeda // of that field) and we require that field to be `IntoBytes` (meaning 34b437b383SMiguel Ojeda // there's no padding in that field). 35b437b383SMiguel Ojeda // - repr(packed): Any inter-field padding bytes are removed, meaning 36b437b383SMiguel Ojeda // that any padding bytes would need to come from the fields, all of 37b437b383SMiguel Ojeda // which we require to be `IntoBytes` (meaning they don't have any 38b437b383SMiguel Ojeda // padding). Note that this holds regardless of other `repr` 39b437b383SMiguel Ojeda // attributes, including `repr(Rust)`. [1] 40b437b383SMiguel Ojeda // 41b437b383SMiguel Ojeda // [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#the-alignment-modifiers: 42b437b383SMiguel Ojeda // 43b437b383SMiguel Ojeda // An important consequence of these rules is that a type with 44b437b383SMiguel Ojeda // `#[repr(packed(1))]`` (or `#[repr(packed)]``) will have no 45b437b383SMiguel Ojeda // inter-field padding. 46b437b383SMiguel Ojeda (None, false) 47b437b383SMiguel Ojeda } else if is_c && !repr.is_align_gt_1() && num_fields <= 1 { 48b437b383SMiguel Ojeda // No padding check needed. A repr(C) struct with zero or one field has 49b437b383SMiguel Ojeda // no padding unless #[repr(align)] explicitly adds padding, which we 50b437b383SMiguel Ojeda // check for in this branch's condition. 51b437b383SMiguel Ojeda (None, false) 52b437b383SMiguel Ojeda } else if ctx.ast.generics.params.is_empty() { 53b437b383SMiguel Ojeda // Is the last field a syntactic slice, i.e., `[SomeType]`. 54b437b383SMiguel Ojeda let is_syntactic_dst = 55b437b383SMiguel Ojeda strct.fields().last().map(|(_, _, ty)| matches!(ty, Type::Slice(_))).unwrap_or(false); 56b437b383SMiguel Ojeda // Since there are no generics, we can emit a padding check. All reprs 57b437b383SMiguel Ojeda // guarantee that fields won't overlap [1], so the padding check is 58b437b383SMiguel Ojeda // sound. This is more permissive than the next case, which requires 59b437b383SMiguel Ojeda // that all field types implement `Unaligned`. 60b437b383SMiguel Ojeda // 61b437b383SMiguel Ojeda // [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#the-rust-representation: 62b437b383SMiguel Ojeda // 63b437b383SMiguel Ojeda // The only data layout guarantees made by [`repr(Rust)`] are those 64b437b383SMiguel Ojeda // required for soundness. They are: 65b437b383SMiguel Ojeda // ... 66b437b383SMiguel Ojeda // 2. The fields do not overlap. 67b437b383SMiguel Ojeda // ... 68b437b383SMiguel Ojeda if is_c && is_syntactic_dst { 69b437b383SMiguel Ojeda (Some(PaddingCheck::ReprCStruct), false) 70b437b383SMiguel Ojeda } else { 71b437b383SMiguel Ojeda (Some(PaddingCheck::Struct), false) 72b437b383SMiguel Ojeda } 73b437b383SMiguel Ojeda } else if is_c && !repr.is_align_gt_1() { 74b437b383SMiguel Ojeda // We can't use a padding check since there are generic type arguments. 75b437b383SMiguel Ojeda // Instead, we require all field types to implement `Unaligned`. This 76b437b383SMiguel Ojeda // ensures that the `repr(C)` layout algorithm will not insert any 77b437b383SMiguel Ojeda // padding unless #[repr(align)] explicitly adds padding, which we check 78b437b383SMiguel Ojeda // for in this branch's condition. 79b437b383SMiguel Ojeda // 80b437b383SMiguel Ojeda // FIXME(#10): Support type parameters for non-transparent, non-packed 81b437b383SMiguel Ojeda // structs without requiring `Unaligned`. 82b437b383SMiguel Ojeda (None, true) 83b437b383SMiguel Ojeda } else { 84b437b383SMiguel Ojeda return ctx.error_or_skip(Error::new( 85b437b383SMiguel Ojeda Span::call_site(), 86b437b383SMiguel Ojeda "must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout", 87b437b383SMiguel Ojeda )); 88b437b383SMiguel Ojeda }; 89b437b383SMiguel Ojeda 90b437b383SMiguel Ojeda let field_bounds = if require_unaligned_fields { 91b437b383SMiguel Ojeda FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Unaligned)]) 92b437b383SMiguel Ojeda } else { 93b437b383SMiguel Ojeda FieldBounds::ALL_SELF 94b437b383SMiguel Ojeda }; 95b437b383SMiguel Ojeda 96b437b383SMiguel Ojeda Ok(ImplBlockBuilder::new(ctx, strct, Trait::IntoBytes, field_bounds) 97b437b383SMiguel Ojeda .padding_check(padding_check) 98b437b383SMiguel Ojeda .build()) 99b437b383SMiguel Ojeda } 100b437b383SMiguel Ojeda 101b437b383SMiguel Ojeda fn derive_into_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> { 102b437b383SMiguel Ojeda let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?; 103b437b383SMiguel Ojeda if !repr.is_c() && !repr.is_primitive() { 104b437b383SMiguel Ojeda return ctx.error_or_skip(Error::new( 105b437b383SMiguel Ojeda Span::call_site(), 106b437b383SMiguel Ojeda "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout", 107b437b383SMiguel Ojeda )); 108b437b383SMiguel Ojeda } 109b437b383SMiguel Ojeda 110b437b383SMiguel Ojeda let tag_type_definition = generate_tag_enum(ctx, &repr, enm); 111b437b383SMiguel Ojeda Ok(ImplBlockBuilder::new(ctx, enm, Trait::IntoBytes, FieldBounds::ALL_SELF) 112b437b383SMiguel Ojeda .padding_check(PaddingCheck::Enum { tag_type_definition }) 113b437b383SMiguel Ojeda .build()) 114b437b383SMiguel Ojeda } 115b437b383SMiguel Ojeda 116b437b383SMiguel Ojeda fn derive_into_bytes_union(ctx: &Ctx, unn: &DataUnion) -> Result<TokenStream, Error> { 117b437b383SMiguel Ojeda // See #1792 for more context. 118b437b383SMiguel Ojeda // 119b437b383SMiguel Ojeda // By checking for `zerocopy_derive_union_into_bytes` both here and in the 120b437b383SMiguel Ojeda // generated code, we ensure that `--cfg zerocopy_derive_union_into_bytes` 121b437b383SMiguel Ojeda // need only be passed *either* when compiling this crate *or* when 122b437b383SMiguel Ojeda // compiling the user's crate. The former is preferable, but in some 123b437b383SMiguel Ojeda // situations (such as when cross-compiling using `cargo build --target`), 124b437b383SMiguel Ojeda // it doesn't get propagated to this crate's build by default. 125b437b383SMiguel Ojeda let cfg_compile_error = if cfg!(zerocopy_derive_union_into_bytes) { 126b437b383SMiguel Ojeda quote!() 127b437b383SMiguel Ojeda } else { 128b437b383SMiguel Ojeda let core = ctx.core_path(); 129b437b383SMiguel Ojeda let error_message = "requires --cfg zerocopy_derive_union_into_bytes; 130b437b383SMiguel Ojeda please let us know you use this feature: https://github.com/google/zerocopy/discussions/1802"; 131b437b383SMiguel Ojeda quote!( 132b437b383SMiguel Ojeda #[allow(unused_attributes, unexpected_cfgs)] 133b437b383SMiguel Ojeda const _: () = { 134b437b383SMiguel Ojeda #[cfg(not(zerocopy_derive_union_into_bytes))] 135b437b383SMiguel Ojeda #core::compile_error!(#error_message); 136b437b383SMiguel Ojeda }; 137b437b383SMiguel Ojeda ) 138b437b383SMiguel Ojeda }; 139b437b383SMiguel Ojeda 140b437b383SMiguel Ojeda // FIXME(#10): Support type parameters. 141b437b383SMiguel Ojeda if !ctx.ast.generics.params.is_empty() { 142b437b383SMiguel Ojeda return ctx.error_or_skip(Error::new( 143b437b383SMiguel Ojeda Span::call_site(), 144b437b383SMiguel Ojeda "unsupported on types with type parameters", 145b437b383SMiguel Ojeda )); 146b437b383SMiguel Ojeda } 147b437b383SMiguel Ojeda 148b437b383SMiguel Ojeda // Because we don't support generics, we don't need to worry about 149b437b383SMiguel Ojeda // special-casing different reprs. So long as there is *some* repr which 150b437b383SMiguel Ojeda // guarantees the layout, our `PaddingCheck::Union` guarantees that there is 151b437b383SMiguel Ojeda // no padding. 152b437b383SMiguel Ojeda let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?; 153b437b383SMiguel Ojeda if !repr.is_c() && !repr.is_transparent() && !repr.is_packed_1() { 154b437b383SMiguel Ojeda return ctx.error_or_skip(Error::new( 155b437b383SMiguel Ojeda Span::call_site(), 156b437b383SMiguel Ojeda "must be #[repr(C)], #[repr(packed)], or #[repr(transparent)]", 157b437b383SMiguel Ojeda )); 158b437b383SMiguel Ojeda } 159b437b383SMiguel Ojeda 160b437b383SMiguel Ojeda let impl_block = ImplBlockBuilder::new(ctx, unn, Trait::IntoBytes, FieldBounds::ALL_SELF) 161b437b383SMiguel Ojeda .padding_check(PaddingCheck::Union) 162b437b383SMiguel Ojeda .build(); 163b437b383SMiguel Ojeda Ok(quote!(#cfg_compile_error #impl_block)) 164b437b383SMiguel Ojeda } 165