Skip to main content

zerocopy_derive/derive/
into_bytes.rs

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