zerocopy_derive/
lib.rs

1// Copyright 2019 The Fuchsia Authors
2//
3// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6// This file may not be copied, modified, or distributed except according to
7// those terms.
8
9//! Derive macros for [zerocopy]'s traits.
10//!
11//! [zerocopy]: https://docs.rs/zerocopy
12
13// Sometimes we want to use lints which were added after our MSRV.
14// `unknown_lints` is `warn` by default and we deny warnings in CI, so without
15// this attribute, any unknown lint would cause a CI failure when testing with
16// our MSRV.
17#![allow(unknown_lints)]
18#![deny(renamed_and_removed_lints)]
19#![deny(clippy::all, clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks)]
20#![deny(
21    rustdoc::bare_urls,
22    rustdoc::broken_intra_doc_links,
23    rustdoc::invalid_codeblock_attributes,
24    rustdoc::invalid_html_tags,
25    rustdoc::invalid_rust_codeblocks,
26    rustdoc::missing_crate_level_docs,
27    rustdoc::private_intra_doc_links
28)]
29#![recursion_limit = "128"]
30
31mod ext;
32mod repr;
33
34use {
35    proc_macro2::Span,
36    quote::quote,
37    syn::{
38        parse_quote, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, Expr, ExprLit,
39        GenericParam, Ident, Lit,
40    },
41};
42
43use {crate::ext::*, crate::repr::*};
44
45// Unwraps a `Result<_, Vec<Error>>`, converting any `Err` value into a
46// `TokenStream` and returning it.
47macro_rules! try_or_print {
48    ($e:expr) => {
49        match $e {
50            Ok(x) => x,
51            Err(errors) => return print_all_errors(errors).into(),
52        }
53    };
54}
55
56// TODO(https://github.com/rust-lang/rust/issues/54140): Some errors could be
57// made better if we could add multiple lines of error output like this:
58//
59// error: unsupported representation
60//   --> enum.rs:28:8
61//    |
62// 28 | #[repr(transparent)]
63//    |
64// help: required by the derive of FromBytes
65//
66// Instead, we have more verbose error messages like "unsupported representation
67// for deriving FromZeroes, FromBytes, AsBytes, or Unaligned on an enum"
68//
69// This will probably require Span::error
70// (https://doc.rust-lang.org/nightly/proc_macro/struct.Span.html#method.error),
71// which is currently unstable. Revisit this once it's stable.
72
73#[proc_macro_derive(KnownLayout)]
74pub fn derive_known_layout(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
75    let ast = syn::parse_macro_input!(ts as DeriveInput);
76
77    let is_repr_c_struct = match &ast.data {
78        Data::Struct(..) => {
79            let reprs = try_or_print!(repr::reprs::<Repr>(&ast.attrs));
80            if reprs.iter().any(|(_meta, repr)| repr == &Repr::C) {
81                Some(reprs)
82            } else {
83                None
84            }
85        }
86        Data::Enum(..) | Data::Union(..) => None,
87    };
88
89    let fields = ast.data.field_types();
90
91    let (require_self_sized, extras) = if let (
92        Some(reprs),
93        Some((trailing_field, leading_fields)),
94    ) = (is_repr_c_struct, fields.split_last())
95    {
96        let repr_align = reprs
97            .iter()
98            .find_map(
99                |(_meta, repr)| {
100                    if let Repr::Align(repr_align) = repr {
101                        Some(repr_align)
102                    } else {
103                        None
104                    }
105                },
106            )
107            .map(|repr_align| quote!(NonZeroUsize::new(#repr_align as usize)))
108            .unwrap_or(quote!(None));
109
110        let repr_packed = reprs
111            .iter()
112            .find_map(|(_meta, repr)| match repr {
113                Repr::Packed => Some(1),
114                Repr::PackedN(repr_packed) => Some(*repr_packed),
115                _ => None,
116            })
117            .map(|repr_packed| quote!(NonZeroUsize::new(#repr_packed as usize)))
118            .unwrap_or(quote!(None));
119
120        (
121            false,
122            quote!(
123                // SAFETY: `LAYOUT` accurately describes the layout of `Self`.
124                // The layout of `Self` is reflected using a sequence of
125                // invocations of `DstLayout::{new_zst,extend,pad_to_align}`.
126                // The documentation of these items vows that invocations in
127                // this manner will acurately describe a type, so long as:
128                //
129                //  - that type is `repr(C)`,
130                //  - its fields are enumerated in the order they appear,
131                //  - the presence of `repr_align` and `repr_packed` are correctly accounted for.
132                //
133                // We respect all three of these preconditions here. This
134                // expansion is only used if `is_repr_c_struct`, we enumerate
135                // the fields in order, and we extract the values of `align(N)`
136                // and `packed(N)`.
137                const LAYOUT: ::zerocopy::DstLayout = {
138                    use ::zerocopy::macro_util::core_reexport::num::NonZeroUsize;
139                    use ::zerocopy::{DstLayout, KnownLayout};
140
141                    let repr_align = #repr_align;
142                    let repr_packed = #repr_packed;
143
144                    DstLayout::new_zst(repr_align)
145                        #(.extend(DstLayout::for_type::<#leading_fields>(), repr_packed))*
146                        .extend(<#trailing_field as KnownLayout>::LAYOUT, repr_packed)
147                        .pad_to_align()
148                };
149
150                // SAFETY:
151                // - The recursive call to `raw_from_ptr_len` preserves both address and provenance.
152                // - The `as` cast preserves both address and provenance.
153                // - `NonNull::new_unchecked` preserves both address and provenance.
154                #[inline(always)]
155                fn raw_from_ptr_len(
156                    bytes: ::zerocopy::macro_util::core_reexport::ptr::NonNull<u8>,
157                    elems: usize,
158                ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> {
159                    use ::zerocopy::{KnownLayout};
160                    let trailing = <#trailing_field as KnownLayout>::raw_from_ptr_len(bytes, elems);
161                    let slf = trailing.as_ptr() as *mut Self;
162                    // SAFETY: Constructed from `trailing`, which is non-null.
163                    unsafe { ::zerocopy::macro_util::core_reexport::ptr::NonNull::new_unchecked(slf) }
164                }
165            ),
166        )
167    } else {
168        // For enums, unions, and non-`repr(C)` structs, we require that
169        // `Self` is sized, and as a result don't need to reason about the
170        // internals of the type.
171        (
172            true,
173            quote!(
174                // SAFETY: `LAYOUT` is guaranteed to accurately describe the
175                // layout of `Self`, because that is the documented safety
176                // contract of `DstLayout::for_type`.
177                const LAYOUT: ::zerocopy::DstLayout = ::zerocopy::DstLayout::for_type::<Self>();
178
179                // SAFETY: `.cast` preserves address and provenance.
180                //
181                // TODO(#429): Add documentation to `.cast` that promises that
182                // it preserves provenance.
183                #[inline(always)]
184                fn raw_from_ptr_len(
185                    bytes: ::zerocopy::macro_util::core_reexport::ptr::NonNull<u8>,
186                    _elems: usize,
187                ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> {
188                    bytes.cast::<Self>()
189                }
190            ),
191        )
192    };
193
194    match &ast.data {
195        Data::Struct(strct) => {
196            let require_trait_bound_on_field_types = if require_self_sized {
197                RequireBoundedFields::No
198            } else {
199                RequireBoundedFields::Trailing
200            };
201
202            // A bound on the trailing field is required, since structs are
203            // unsized if their trailing field is unsized. Reflecting the layout
204            // of an usized trailing field requires that the field is
205            // `KnownLayout`.
206            impl_block(
207                &ast,
208                strct,
209                Trait::KnownLayout,
210                require_trait_bound_on_field_types,
211                require_self_sized,
212                None,
213                Some(extras),
214            )
215        }
216        Data::Enum(enm) => {
217            // A bound on the trailing field is not required, since enums cannot
218            // currently be unsized.
219            impl_block(
220                &ast,
221                enm,
222                Trait::KnownLayout,
223                RequireBoundedFields::No,
224                true,
225                None,
226                Some(extras),
227            )
228        }
229        Data::Union(unn) => {
230            // A bound on the trailing field is not required, since unions
231            // cannot currently be unsized.
232            impl_block(
233                &ast,
234                unn,
235                Trait::KnownLayout,
236                RequireBoundedFields::No,
237                true,
238                None,
239                Some(extras),
240            )
241        }
242    }
243    .into()
244}
245
246#[proc_macro_derive(FromZeroes)]
247pub fn derive_from_zeroes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
248    let ast = syn::parse_macro_input!(ts as DeriveInput);
249    match &ast.data {
250        Data::Struct(strct) => derive_from_zeroes_struct(&ast, strct),
251        Data::Enum(enm) => derive_from_zeroes_enum(&ast, enm),
252        Data::Union(unn) => derive_from_zeroes_union(&ast, unn),
253    }
254    .into()
255}
256
257#[proc_macro_derive(FromBytes)]
258pub fn derive_from_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
259    let ast = syn::parse_macro_input!(ts as DeriveInput);
260    match &ast.data {
261        Data::Struct(strct) => derive_from_bytes_struct(&ast, strct),
262        Data::Enum(enm) => derive_from_bytes_enum(&ast, enm),
263        Data::Union(unn) => derive_from_bytes_union(&ast, unn),
264    }
265    .into()
266}
267
268#[proc_macro_derive(AsBytes)]
269pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
270    let ast = syn::parse_macro_input!(ts as DeriveInput);
271    match &ast.data {
272        Data::Struct(strct) => derive_as_bytes_struct(&ast, strct),
273        Data::Enum(enm) => derive_as_bytes_enum(&ast, enm),
274        Data::Union(unn) => derive_as_bytes_union(&ast, unn),
275    }
276    .into()
277}
278
279#[proc_macro_derive(Unaligned)]
280pub fn derive_unaligned(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
281    let ast = syn::parse_macro_input!(ts as DeriveInput);
282    match &ast.data {
283        Data::Struct(strct) => derive_unaligned_struct(&ast, strct),
284        Data::Enum(enm) => derive_unaligned_enum(&ast, enm),
285        Data::Union(unn) => derive_unaligned_union(&ast, unn),
286    }
287    .into()
288}
289
290const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr]] = &[
291    &[StructRepr::C],
292    &[StructRepr::Transparent],
293    &[StructRepr::Packed],
294    &[StructRepr::C, StructRepr::Packed],
295];
296
297// A struct is `FromZeroes` if:
298// - all fields are `FromZeroes`
299
300fn derive_from_zeroes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
301    impl_block(ast, strct, Trait::FromZeroes, RequireBoundedFields::Yes, false, None, None)
302}
303
304// An enum is `FromZeroes` if:
305// - all of its variants are fieldless
306// - one of the variants has a discriminant of `0`
307
308fn derive_from_zeroes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream {
309    if !enm.is_c_like() {
310        return Error::new_spanned(ast, "only C-like enums can implement FromZeroes")
311            .to_compile_error();
312    }
313
314    let has_explicit_zero_discriminant =
315        enm.variants.iter().filter_map(|v| v.discriminant.as_ref()).any(|(_, e)| {
316            if let Expr::Lit(ExprLit { lit: Lit::Int(i), .. }) = e {
317                i.base10_parse::<usize>().ok() == Some(0)
318            } else {
319                false
320            }
321        });
322    // If the first variant of an enum does not specify its discriminant, it is set to zero:
323    // https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-fieldless-enumerations
324    let has_implicit_zero_discriminant =
325        enm.variants.iter().next().map(|v| v.discriminant.is_none()) == Some(true);
326
327    if !has_explicit_zero_discriminant && !has_implicit_zero_discriminant {
328        return Error::new_spanned(
329            ast,
330            "FromZeroes only supported on enums with a variant that has a discriminant of `0`",
331        )
332        .to_compile_error();
333    }
334
335    impl_block(ast, enm, Trait::FromZeroes, RequireBoundedFields::Yes, false, None, None)
336}
337
338// Like structs, unions are `FromZeroes` if
339// - all fields are `FromZeroes`
340
341fn derive_from_zeroes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
342    impl_block(ast, unn, Trait::FromZeroes, RequireBoundedFields::Yes, false, None, None)
343}
344
345// A struct is `FromBytes` if:
346// - all fields are `FromBytes`
347
348fn derive_from_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
349    impl_block(ast, strct, Trait::FromBytes, RequireBoundedFields::Yes, false, None, None)
350}
351
352// An enum is `FromBytes` if:
353// - Every possible bit pattern must be valid, which means that every bit
354//   pattern must correspond to a different enum variant. Thus, for an enum
355//   whose layout takes up N bytes, there must be 2^N variants.
356// - Since we must know N, only representations which guarantee the layout's
357//   size are allowed. These are `repr(uN)` and `repr(iN)` (`repr(C)` implies an
358//   implementation-defined size). `usize` and `isize` technically guarantee the
359//   layout's size, but would require us to know how large those are on the
360//   target platform. This isn't terribly difficult - we could emit a const
361//   expression that could call `core::mem::size_of` in order to determine the
362//   size and check against the number of enum variants, but a) this would be
363//   platform-specific and, b) even on Rust's smallest bit width platform (32),
364//   this would require ~4 billion enum variants, which obviously isn't a thing.
365
366fn derive_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream {
367    if !enm.is_c_like() {
368        return Error::new_spanned(ast, "only C-like enums can implement FromBytes")
369            .to_compile_error();
370    }
371
372    let reprs = try_or_print!(ENUM_FROM_BYTES_CFG.validate_reprs(ast));
373
374    let variants_required = match reprs.as_slice() {
375        [EnumRepr::U8] | [EnumRepr::I8] => 1usize << 8,
376        [EnumRepr::U16] | [EnumRepr::I16] => 1usize << 16,
377        // `validate_reprs` has already validated that it's one of the preceding
378        // patterns.
379        _ => unreachable!(),
380    };
381    if enm.variants.len() != variants_required {
382        return Error::new_spanned(
383            ast,
384            format!(
385                "FromBytes only supported on {} enum with {} variants",
386                reprs[0], variants_required
387            ),
388        )
389        .to_compile_error();
390    }
391
392    impl_block(ast, enm, Trait::FromBytes, RequireBoundedFields::Yes, false, None, None)
393}
394
395#[rustfmt::skip]
396const ENUM_FROM_BYTES_CFG: Config<EnumRepr> = {
397    use EnumRepr::*;
398    Config {
399        allowed_combinations_message: r#"FromBytes requires repr of "u8", "u16", "i8", or "i16""#,
400        derive_unaligned: false,
401        allowed_combinations: &[
402            &[U8],
403            &[U16],
404            &[I8],
405            &[I16],
406        ],
407        disallowed_but_legal_combinations: &[
408            &[C],
409            &[U32],
410            &[I32],
411            &[U64],
412            &[I64],
413            &[Usize],
414            &[Isize],
415        ],
416    }
417};
418
419// Like structs, unions are `FromBytes` if
420// - all fields are `FromBytes`
421
422fn derive_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
423    impl_block(ast, unn, Trait::FromBytes, RequireBoundedFields::Yes, false, None, None)
424}
425
426// A struct is `AsBytes` if:
427// - all fields are `AsBytes`
428// - `repr(C)` or `repr(transparent)` and
429//   - no padding (size of struct equals sum of size of field types)
430// - `repr(packed)`
431
432fn derive_as_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
433    let reprs = try_or_print!(STRUCT_UNION_AS_BYTES_CFG.validate_reprs(ast));
434    let is_transparent = reprs.contains(&StructRepr::Transparent);
435    let is_packed = reprs.contains(&StructRepr::Packed);
436
437    // TODO(#10): Support type parameters for non-transparent, non-packed
438    // structs.
439    if !ast.generics.params.is_empty() && !is_transparent && !is_packed {
440        return Error::new(
441            Span::call_site(),
442            "unsupported on generic structs that are not repr(transparent) or repr(packed)",
443        )
444        .to_compile_error();
445    }
446
447    // We don't need a padding check if the struct is repr(transparent) or
448    // repr(packed).
449    // - repr(transparent): The layout and ABI of the whole struct is the same
450    //   as its only non-ZST field (meaning there's no padding outside of that
451    //   field) and we require that field to be `AsBytes` (meaning there's no
452    //   padding in that field).
453    // - repr(packed): Any inter-field padding bytes are removed, meaning that
454    //   any padding bytes would need to come from the fields, all of which
455    //   we require to be `AsBytes` (meaning they don't have any padding).
456    let padding_check = if is_transparent || is_packed { None } else { Some(PaddingCheck::Struct) };
457    impl_block(ast, strct, Trait::AsBytes, RequireBoundedFields::Yes, false, padding_check, None)
458}
459
460const STRUCT_UNION_AS_BYTES_CFG: Config<StructRepr> = Config {
461    // Since `disallowed_but_legal_combinations` is empty, this message will
462    // never actually be emitted.
463    allowed_combinations_message: r#"AsBytes requires either a) repr "C" or "transparent" with all fields implementing AsBytes or, b) repr "packed""#,
464    derive_unaligned: false,
465    allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS,
466    disallowed_but_legal_combinations: &[],
467};
468
469// An enum is `AsBytes` if it is C-like and has a defined repr.
470
471fn derive_as_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream {
472    if !enm.is_c_like() {
473        return Error::new_spanned(ast, "only C-like enums can implement AsBytes")
474            .to_compile_error();
475    }
476
477    // We don't care what the repr is; we only care that it is one of the
478    // allowed ones.
479    let _: Vec<repr::EnumRepr> = try_or_print!(ENUM_AS_BYTES_CFG.validate_reprs(ast));
480    impl_block(ast, enm, Trait::AsBytes, RequireBoundedFields::No, false, None, None)
481}
482
483#[rustfmt::skip]
484const ENUM_AS_BYTES_CFG: Config<EnumRepr> = {
485    use EnumRepr::*;
486    Config {
487        // Since `disallowed_but_legal_combinations` is empty, this message will
488        // never actually be emitted.
489        allowed_combinations_message: r#"AsBytes requires repr of "C", "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", or "isize""#,
490        derive_unaligned: false,
491        allowed_combinations: &[
492            &[C],
493            &[U8],
494            &[U16],
495            &[I8],
496            &[I16],
497            &[U32],
498            &[I32],
499            &[U64],
500            &[I64],
501            &[Usize],
502            &[Isize],
503        ],
504        disallowed_but_legal_combinations: &[],
505    }
506};
507
508// A union is `AsBytes` if:
509// - all fields are `AsBytes`
510// - `repr(C)`, `repr(transparent)`, or `repr(packed)`
511// - no padding (size of union equals size of each field type)
512
513fn derive_as_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
514    // TODO(#10): Support type parameters.
515    if !ast.generics.params.is_empty() {
516        return Error::new(Span::call_site(), "unsupported on types with type parameters")
517            .to_compile_error();
518    }
519
520    try_or_print!(STRUCT_UNION_AS_BYTES_CFG.validate_reprs(ast));
521
522    impl_block(
523        ast,
524        unn,
525        Trait::AsBytes,
526        RequireBoundedFields::Yes,
527        false,
528        Some(PaddingCheck::Union),
529        None,
530    )
531}
532
533// A struct is `Unaligned` if:
534// - `repr(align)` is no more than 1 and either
535//   - `repr(C)` or `repr(transparent)` and
536//     - all fields `Unaligned`
537//   - `repr(packed)`
538
539fn derive_unaligned_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
540    let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast));
541    let require_trait_bounds_on_field_types = (!reprs.contains(&StructRepr::Packed)).into();
542
543    impl_block(ast, strct, Trait::Unaligned, require_trait_bounds_on_field_types, false, None, None)
544}
545
546const STRUCT_UNION_UNALIGNED_CFG: Config<StructRepr> = Config {
547    // Since `disallowed_but_legal_combinations` is empty, this message will
548    // never actually be emitted.
549    allowed_combinations_message: r#"Unaligned requires either a) repr "C" or "transparent" with all fields implementing Unaligned or, b) repr "packed""#,
550    derive_unaligned: true,
551    allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS,
552    disallowed_but_legal_combinations: &[],
553};
554
555// An enum is `Unaligned` if:
556// - No `repr(align(N > 1))`
557// - `repr(u8)` or `repr(i8)`
558
559fn derive_unaligned_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream {
560    if !enm.is_c_like() {
561        return Error::new_spanned(ast, "only C-like enums can implement Unaligned")
562            .to_compile_error();
563    }
564
565    // The only valid reprs are `u8` and `i8`, and optionally `align(1)`. We
566    // don't actually care what the reprs are so long as they satisfy that
567    // requirement.
568    let _: Vec<repr::EnumRepr> = try_or_print!(ENUM_UNALIGNED_CFG.validate_reprs(ast));
569
570    // C-like enums cannot currently have type parameters, so this value of true
571    // for `require_trait_bound_on_field_types` doesn't really do anything. But
572    // it's marginally more future-proof in case that restriction is lifted in
573    // the future.
574    impl_block(ast, enm, Trait::Unaligned, RequireBoundedFields::Yes, false, None, None)
575}
576
577#[rustfmt::skip]
578const ENUM_UNALIGNED_CFG: Config<EnumRepr> = {
579    use EnumRepr::*;
580    Config {
581        allowed_combinations_message:
582            r#"Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1)))"#,
583        derive_unaligned: true,
584        allowed_combinations: &[
585            &[U8],
586            &[I8],
587        ],
588        disallowed_but_legal_combinations: &[
589            &[C],
590            &[U16],
591            &[U32],
592            &[U64],
593            &[Usize],
594            &[I16],
595            &[I32],
596            &[I64],
597            &[Isize],
598        ],
599    }
600};
601
602// Like structs, a union is `Unaligned` if:
603// - `repr(align)` is no more than 1 and either
604//   - `repr(C)` or `repr(transparent)` and
605//     - all fields `Unaligned`
606//   - `repr(packed)`
607
608fn derive_unaligned_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
609    let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast));
610    let require_trait_bound_on_field_types = (!reprs.contains(&StructRepr::Packed)).into();
611
612    impl_block(ast, unn, Trait::Unaligned, require_trait_bound_on_field_types, false, None, None)
613}
614
615// This enum describes what kind of padding check needs to be generated for the
616// associated impl.
617enum PaddingCheck {
618    // Check that the sum of the fields' sizes exactly equals the struct's size.
619    Struct,
620    // Check that the size of each field exactly equals the union's size.
621    Union,
622}
623
624impl PaddingCheck {
625    /// Returns the ident of the macro to call in order to validate that a type
626    /// passes the padding check encoded by `PaddingCheck`.
627    fn validator_macro_ident(&self) -> Ident {
628        let s = match self {
629            PaddingCheck::Struct => "struct_has_padding",
630            PaddingCheck::Union => "union_has_padding",
631        };
632
633        Ident::new(s, Span::call_site())
634    }
635}
636
637#[derive(Debug, Eq, PartialEq)]
638enum Trait {
639    KnownLayout,
640    FromZeroes,
641    FromBytes,
642    AsBytes,
643    Unaligned,
644}
645
646impl Trait {
647    fn ident(&self) -> Ident {
648        Ident::new(format!("{:?}", self).as_str(), Span::call_site())
649    }
650}
651
652#[derive(Debug, Eq, PartialEq)]
653enum RequireBoundedFields {
654    No,
655    Yes,
656    Trailing,
657}
658
659impl From<bool> for RequireBoundedFields {
660    fn from(do_require: bool) -> Self {
661        match do_require {
662            true => Self::Yes,
663            false => Self::No,
664        }
665    }
666}
667
668fn impl_block<D: DataExt>(
669    input: &DeriveInput,
670    data: &D,
671    trt: Trait,
672    require_trait_bound_on_field_types: RequireBoundedFields,
673    require_self_sized: bool,
674    padding_check: Option<PaddingCheck>,
675    extras: Option<proc_macro2::TokenStream>,
676) -> proc_macro2::TokenStream {
677    // In this documentation, we will refer to this hypothetical struct:
678    //
679    //   #[derive(FromBytes)]
680    //   struct Foo<T, I: Iterator>
681    //   where
682    //       T: Copy,
683    //       I: Clone,
684    //       I::Item: Clone,
685    //   {
686    //       a: u8,
687    //       b: T,
688    //       c: I::Item,
689    //   }
690    //
691    // We extract the field types, which in this case are `u8`, `T`, and
692    // `I::Item`. We re-use the existing parameters and where clauses. If
693    // `require_trait_bound == true` (as it is for `FromBytes), we add where
694    // bounds for each field's type:
695    //
696    //   impl<T, I: Iterator> FromBytes for Foo<T, I>
697    //   where
698    //       T: Copy,
699    //       I: Clone,
700    //       I::Item: Clone,
701    //       T: FromBytes,
702    //       I::Item: FromBytes,
703    //   {
704    //   }
705    //
706    // NOTE: It is standard practice to only emit bounds for the type parameters
707    // themselves, not for field types based on those parameters (e.g., `T` vs
708    // `T::Foo`). For a discussion of why this is standard practice, see
709    // https://github.com/rust-lang/rust/issues/26925.
710    //
711    // The reason we diverge from this standard is that doing it that way for us
712    // would be unsound. E.g., consider a type, `T` where `T: FromBytes` but
713    // `T::Foo: !FromBytes`. It would not be sound for us to accept a type with
714    // a `T::Foo` field as `FromBytes` simply because `T: FromBytes`.
715    //
716    // While there's no getting around this requirement for us, it does have the
717    // pretty serious downside that, when lifetimes are involved, the trait
718    // solver ties itself in knots:
719    //
720    //     #[derive(Unaligned)]
721    //     #[repr(C)]
722    //     struct Dup<'a, 'b> {
723    //         a: PhantomData<&'a u8>,
724    //         b: PhantomData<&'b u8>,
725    //     }
726    //
727    //     error[E0283]: type annotations required: cannot resolve `core::marker::PhantomData<&'a u8>: zerocopy::Unaligned`
728    //      --> src/main.rs:6:10
729    //       |
730    //     6 | #[derive(Unaligned)]
731    //       |          ^^^^^^^^^
732    //       |
733    //       = note: required by `zerocopy::Unaligned`
734
735    let type_ident = &input.ident;
736    let trait_ident = trt.ident();
737    let field_types = data.field_types();
738
739    let bound_tt = |ty| parse_quote!(#ty: ::zerocopy::#trait_ident);
740    let field_type_bounds: Vec<_> = match (require_trait_bound_on_field_types, &field_types[..]) {
741        (RequireBoundedFields::Yes, _) => field_types.iter().map(bound_tt).collect(),
742        (RequireBoundedFields::No, _) | (RequireBoundedFields::Trailing, []) => vec![],
743        (RequireBoundedFields::Trailing, [.., last]) => vec![bound_tt(last)],
744    };
745
746    // Don't bother emitting a padding check if there are no fields.
747    #[allow(
748        unstable_name_collisions, // See `BoolExt` below
749        clippy::incompatible_msrv, // https://github.com/rust-lang/rust-clippy/issues/12280
750    )]
751    let padding_check_bound = padding_check.and_then(|check| (!field_types.is_empty()).then_some(check)).map(|check| {
752        let fields = field_types.iter();
753        let validator_macro = check.validator_macro_ident();
754        parse_quote!(
755            ::zerocopy::macro_util::HasPadding<#type_ident, {::zerocopy::#validator_macro!(#type_ident, #(#fields),*)}>:
756                ::zerocopy::macro_util::ShouldBe<false>
757        )
758    });
759
760    let self_sized_bound = if require_self_sized { Some(parse_quote!(Self: Sized)) } else { None };
761
762    let bounds = input
763        .generics
764        .where_clause
765        .as_ref()
766        .map(|where_clause| where_clause.predicates.iter())
767        .into_iter()
768        .flatten()
769        .chain(field_type_bounds.iter())
770        .chain(padding_check_bound.iter())
771        .chain(self_sized_bound.iter());
772
773    // The parameters with trait bounds, but without type defaults.
774    let params = input.generics.params.clone().into_iter().map(|mut param| {
775        match &mut param {
776            GenericParam::Type(ty) => ty.default = None,
777            GenericParam::Const(cnst) => cnst.default = None,
778            GenericParam::Lifetime(_) => {}
779        }
780        quote!(#param)
781    });
782
783    // The identifiers of the parameters without trait bounds or type defaults.
784    let param_idents = input.generics.params.iter().map(|param| match param {
785        GenericParam::Type(ty) => {
786            let ident = &ty.ident;
787            quote!(#ident)
788        }
789        GenericParam::Lifetime(l) => {
790            let ident = &l.lifetime;
791            quote!(#ident)
792        }
793        GenericParam::Const(cnst) => {
794            let ident = &cnst.ident;
795            quote!({#ident})
796        }
797    });
798
799    quote! {
800        // TODO(#553): Add a test that generates a warning when
801        // `#[allow(deprecated)]` isn't present.
802        #[allow(deprecated)]
803        unsafe impl < #(#params),* > ::zerocopy::#trait_ident for #type_ident < #(#param_idents),* >
804        where
805            #(#bounds,)*
806        {
807            fn only_derive_is_allowed_to_implement_this_trait() {}
808
809            #extras
810        }
811    }
812}
813
814fn print_all_errors(errors: Vec<Error>) -> proc_macro2::TokenStream {
815    errors.iter().map(Error::to_compile_error).collect()
816}
817
818// A polyfill for `Option::then_some`, which was added after our MSRV.
819//
820// TODO(#67): Remove this once our MSRV is >= 1.62.
821#[allow(unused)]
822trait BoolExt {
823    fn then_some<T>(self, t: T) -> Option<T>;
824}
825
826#[allow(unused)]
827impl BoolExt for bool {
828    fn then_some<T>(self, t: T) -> Option<T> {
829        if self {
830            Some(t)
831        } else {
832            None
833        }
834    }
835}
836
837#[cfg(test)]
838mod tests {
839    use super::*;
840
841    #[test]
842    fn test_config_repr_orderings() {
843        // Validate that the repr lists in the various configs are in the
844        // canonical order. If they aren't, then our algorithm to look up in
845        // those lists won't work.
846
847        // TODO(https://github.com/rust-lang/rust/issues/53485): Remove once
848        // `Vec::is_sorted` is stabilized.
849        fn is_sorted_and_deduped<T: Clone + Ord>(ts: &[T]) -> bool {
850            let mut sorted = ts.to_vec();
851            sorted.sort();
852            sorted.dedup();
853            ts == sorted.as_slice()
854        }
855
856        fn elements_are_sorted_and_deduped<T: Clone + Ord>(lists: &[&[T]]) -> bool {
857            lists.iter().all(|list| is_sorted_and_deduped(list))
858        }
859
860        fn config_is_sorted<T: KindRepr + Clone>(config: &Config<T>) -> bool {
861            elements_are_sorted_and_deduped(config.allowed_combinations)
862                && elements_are_sorted_and_deduped(config.disallowed_but_legal_combinations)
863        }
864
865        assert!(config_is_sorted(&STRUCT_UNION_UNALIGNED_CFG));
866        assert!(config_is_sorted(&ENUM_FROM_BYTES_CFG));
867        assert!(config_is_sorted(&ENUM_UNALIGNED_CFG));
868    }
869
870    #[test]
871    fn test_config_repr_no_overlap() {
872        // Validate that no set of reprs appears in both the
873        // `allowed_combinations` and `disallowed_but_legal_combinations` lists.
874
875        fn overlap<T: Eq>(a: &[T], b: &[T]) -> bool {
876            a.iter().any(|elem| b.contains(elem))
877        }
878
879        fn config_overlaps<T: KindRepr + Eq>(config: &Config<T>) -> bool {
880            overlap(config.allowed_combinations, config.disallowed_but_legal_combinations)
881        }
882
883        assert!(!config_overlaps(&STRUCT_UNION_UNALIGNED_CFG));
884        assert!(!config_overlaps(&ENUM_FROM_BYTES_CFG));
885        assert!(!config_overlaps(&ENUM_UNALIGNED_CFG));
886    }
887}