1#![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
45macro_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#[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 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 #[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 unsafe { ::zerocopy::macro_util::core_reexport::ptr::NonNull::new_unchecked(slf) }
164 }
165 ),
166 )
167 } else {
168 (
172 true,
173 quote!(
174 const LAYOUT: ::zerocopy::DstLayout = ::zerocopy::DstLayout::for_type::<Self>();
178
179 #[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 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 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 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
297fn 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
304fn 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 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
338fn 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
345fn 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
352fn 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 _ => 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
419fn 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
426fn 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 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 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 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
469fn 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 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 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
508fn derive_as_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
514 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
533fn 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 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
555fn 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 let _: Vec<repr::EnumRepr> = try_or_print!(ENUM_UNALIGNED_CFG.validate_reprs(ast));
569
570 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
602fn 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
615enum PaddingCheck {
618 Struct,
620 Union,
622}
623
624impl PaddingCheck {
625 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 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 #[allow(
748 unstable_name_collisions, clippy::incompatible_msrv, )]
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 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 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 #[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#[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 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 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}