xref: /linux/rust/zerocopy-derive/derive/into_bytes.rs (revision 5f85604cf0877b0369dfd68cd50cf61c0f134819)
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