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