1use proc_macro2::TokenStream;
4use quote::quote;
5use syn::{
6 parse_quote, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error,
7 Expr, Fields, Ident, Index, Type,
8};
9
10use crate::{
11 repr::{EnumRepr, StructUnionRepr},
12 util::{
13 const_block, enum_size_from_repr, generate_tag_enum, Ctx, DataExt, FieldBounds,
14 ImplBlockBuilder, Trait, TraitBound,
15 },
16};
17fn tag_ident(variant_ident: &Ident) -> Ident {
18 ident!(("___ZEROCOPY_TAG_{}", variant_ident), variant_ident.span())
19}
20
21fn generate_tag_consts(data: &DataEnum) -> TokenStream {
33 let tags = data.variants.iter().map(|v| {
34 let variant_ident = &v.ident;
35 let tag_ident = tag_ident(variant_ident);
36
37 quote! {
38 const #tag_ident: ___ZerocopyTagPrimitive =
54 ___ZerocopyTag::#variant_ident as ___ZerocopyTagPrimitive;
55 }
56 });
57
58 quote! {
59 #(#tags)*
60 }
61}
62
63fn variant_struct_ident(variant_ident: &Ident) -> Ident {
64 ident!(("___ZerocopyVariantStruct_{}", variant_ident), variant_ident.span())
65}
66
67fn generate_variant_structs(ctx: &Ctx, data: &DataEnum) -> TokenStream {
79 let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
80
81 let enum_name = &ctx.ast.ident;
82
83 let core = ctx.core_path();
87 let phantom_ty = quote! {
88 #core::marker::PhantomData<#enum_name #ty_generics>
89 };
90
91 let variant_structs = data.variants.iter().filter_map(|variant| {
92 if matches!(variant.fields, Fields::Unit) {
95 return None;
96 }
97
98 let variant_struct_ident = variant_struct_ident(&variant.ident);
99 let field_types = variant.fields.iter().map(|f| &f.ty);
100
101 let variant_struct = parse_quote! {
102 #[repr(C)]
103 struct #variant_struct_ident #impl_generics (
104 #core::mem::MaybeUninit<___ZerocopyInnerTag>,
105 #(#field_types,)*
106 #phantom_ty,
107 ) #where_clause;
108 };
109
110 let try_from_bytes_impl =
113 derive_try_from_bytes(&ctx.with_input(&variant_struct), Trait::TryFromBytes)
114 .expect("derive_try_from_bytes should not fail on synthesized type");
115
116 Some(quote! {
117 #variant_struct
118 #try_from_bytes_impl
119 })
120 });
121
122 quote! {
123 #(#variant_structs)*
124 }
125}
126
127fn variants_union_field_ident(ident: &Ident) -> Ident {
128 ident!(("__field_{}", ident), ident.span())
131}
132
133fn generate_variants_union(ctx: &Ctx, data: &DataEnum) -> TokenStream {
134 let generics = &ctx.ast.generics;
135 let (_, ty_generics, _) = generics.split_for_impl();
136
137 let fields = data.variants.iter().filter_map(|variant| {
138 if matches!(variant.fields, Fields::Unit) {
141 return None;
142 }
143
144 let field_name = variants_union_field_ident(&variant.ident);
145 let variant_struct_ident = variant_struct_ident(&variant.ident);
146
147 let core = ctx.core_path();
148 Some(quote! {
149 #field_name: #core::mem::ManuallyDrop<#variant_struct_ident #ty_generics>,
150 })
151 });
152
153 let variants_union = parse_quote! {
154 #[repr(C)]
155 union ___ZerocopyVariants #generics {
156 #(#fields)*
157 __nonempty: (),
163 }
164 };
165
166 let has_field =
167 derive_has_field_struct_union(&ctx.with_input(&variants_union), &variants_union.data);
168
169 quote! {
170 #variants_union
171 #has_field
172 }
173}
174
175pub(crate) fn derive_is_bit_valid(
198 ctx: &Ctx,
199 data: &DataEnum,
200 repr: &EnumRepr,
201) -> Result<TokenStream, Error> {
202 let trait_path = Trait::TryFromBytes.crate_path(ctx);
203 let tag_enum = generate_tag_enum(ctx, repr, data);
204 let tag_consts = generate_tag_consts(data);
205
206 let (outer_tag_type, inner_tag_type) = if repr.is_c() {
207 (quote! { ___ZerocopyTag }, quote! { () })
208 } else if repr.is_primitive() {
209 (quote! { () }, quote! { ___ZerocopyTag })
210 } else {
211 return Err(Error::new(
212 ctx.ast.span(),
213 "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
214 ));
215 };
216
217 let variant_structs = generate_variant_structs(ctx, data);
218 let variants_union = generate_variants_union(ctx, data);
219
220 let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
221
222 let zerocopy_crate = &ctx.zerocopy_crate;
223 let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag, FieldBounds::None)
224 .inner_extras(quote! {
225 type Tag = ___ZerocopyTag;
226 type ProjectToTag = #zerocopy_crate::pointer::cast::CastSized;
227 })
228 .build();
229 let has_fields = data.variants().into_iter().flat_map(|(variant, fields)| {
230 let variant_ident = &variant.unwrap().ident;
231 let variants_union_field_ident = variants_union_field_ident(variant_ident);
232 let field: Box<syn::Type> = parse_quote!(());
233 fields.into_iter().enumerate().map(move |(idx, (vis, ident, ty))| {
234 assert!(matches!(vis, syn::Visibility::Inherited));
238 let variant_struct_field_index = Index::from(idx + 1);
239 let (_, ty_generics, _) = ctx.ast.generics.split_for_impl();
240 let has_field_trait = Trait::HasField {
241 variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
242 field: field.clone(),
246 field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
247 };
248 let has_field_path = has_field_trait.crate_path(ctx);
249 let has_field = ImplBlockBuilder::new(
250 ctx,
251 data,
252 has_field_trait,
253 FieldBounds::None,
254 )
255 .inner_extras(quote! {
256 type Type = #ty;
257
258 #[inline(always)]
259 fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut <Self as #has_field_path>::Type {
260 use #zerocopy_crate::pointer::cast::{CastSized, Projection};
261
262 slf.project::<___ZerocopyRawEnum #ty_generics, CastSized>()
263 .project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(variants) }>>()
264 .project::<_, Projection<_, { #zerocopy_crate::REPR_C_UNION_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variants_union_field_ident) }>>()
265 .project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(value) }>>()
266 .project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variant_struct_field_index) }>>()
267 .as_ptr()
268 }
269 })
270 .build();
271
272 let project = ImplBlockBuilder::new(
273 ctx,
274 data,
275 Trait::ProjectField {
276 variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
277 field: field.clone(),
281 field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
282 invariants: parse_quote!((Aliasing, Alignment, #zerocopy_crate::invariant::Initialized)),
283 },
284 FieldBounds::None,
285 )
286 .param_extras(vec![
287 parse_quote!(Aliasing: #zerocopy_crate::invariant::Aliasing),
288 parse_quote!(Alignment: #zerocopy_crate::invariant::Alignment),
289 ])
290 .inner_extras(quote! {
291 type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
292 type Invariants = (Aliasing, Alignment, #zerocopy_crate::invariant::Initialized);
293 })
294 .build();
295
296 quote! {
297 #has_field
298 #project
299 }
300 })
301 });
302
303 let core = ctx.core_path();
304 let match_arms = data.variants.iter().map(|variant| {
305 let tag_ident = tag_ident(&variant.ident);
306 let variant_struct_ident = variant_struct_ident(&variant.ident);
307 let variants_union_field_ident = variants_union_field_ident(&variant.ident);
308
309 if matches!(variant.fields, Fields::Unit) {
310 quote! {
313 #tag_ident => true
314 }
315 } else {
316 quote! {
317 #tag_ident => {
318 let variant_md = variants.cast::<
322 _,
323 #zerocopy_crate::pointer::cast::Projection<
324 _,
326 { #zerocopy_crate::REPR_C_UNION_VARIANT_ID },
327 { #zerocopy_crate::ident_id!(#variants_union_field_ident) }
328 >,
329 _
330 >();
331 let variant = variant_md.cast::<
332 #zerocopy_crate::ReadOnly<#variant_struct_ident #ty_generics>,
333 #zerocopy_crate::pointer::cast::CastSized,
334 (#zerocopy_crate::pointer::BecauseRead, _)
335 >();
336 <
337 #variant_struct_ident #ty_generics as #trait_path
338 >::is_bit_valid(variant)
339 }
340 }
341 }
342 });
343
344 let generics = &ctx.ast.generics;
345 let raw_enum: DeriveInput = parse_quote! {
346 #[repr(C)]
347 struct ___ZerocopyRawEnum #generics {
348 tag: ___ZerocopyOuterTag,
349 variants: ___ZerocopyVariants #ty_generics,
350 }
351 };
352
353 let self_ident = &ctx.ast.ident;
354 let invariants_eq_impl = quote! {
355 unsafe impl #impl_generics #zerocopy_crate::pointer::InvariantsEq<___ZerocopyRawEnum #ty_generics> for #self_ident #ty_generics #where_clause {}
358 };
359
360 let raw_enum_projections =
361 derive_has_field_struct_union(&ctx.with_input(&raw_enum), &raw_enum.data);
362
363 let raw_enum = quote! {
364 #raw_enum
365 #invariants_eq_impl
366 #raw_enum_projections
367 };
368
369 Ok(quote! {
370 #[inline]
375 fn is_bit_valid<___ZcAlignment>(
376 mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
377 ) -> #core::primitive::bool
378 where
379 ___ZcAlignment: #zerocopy_crate::invariant::Alignment,
380 {
381 #tag_enum
382
383 type ___ZerocopyTagPrimitive = #zerocopy_crate::util::macro_util::SizeToTag<
384 { #core::mem::size_of::<___ZerocopyTag>() },
385 >;
386
387 #tag_consts
388
389 type ___ZerocopyOuterTag = #outer_tag_type;
390 type ___ZerocopyInnerTag = #inner_tag_type;
391
392 #variant_structs
393
394 #variants_union
395
396 #raw_enum
397
398 #has_tag
399
400 #(#has_fields)*
401
402 let tag = {
403 let tag_ptr = unsafe {
418 candidate.reborrow().project_transmute_unchecked::<
419 _,
420 #zerocopy_crate::invariant::Initialized,
421 #zerocopy_crate::pointer::cast::CastSized
422 >()
423 };
424 tag_ptr.recall_validity::<_, (_, (_, _))>().read::<#zerocopy_crate::BecauseImmutable>()
425 };
426
427 let mut raw_enum = candidate.cast::<
428 #zerocopy_crate::ReadOnly<___ZerocopyRawEnum #ty_generics>,
429 #zerocopy_crate::pointer::cast::CastSized,
430 (#zerocopy_crate::pointer::BecauseRead, _)
431 >();
432
433 let variants = #zerocopy_crate::into_inner!(raw_enum.project::<
434 _,
435 { #zerocopy_crate::STRUCT_VARIANT_ID },
436 { #zerocopy_crate::ident_id!(variants) }
437 >());
438
439 match tag {
440 #(#match_arms,)*
441 _ => false,
442 }
443 }
444 })
445}
446pub(crate) fn derive_try_from_bytes(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
447 match &ctx.ast.data {
448 Data::Struct(strct) => derive_try_from_bytes_struct(ctx, strct, top_level),
449 Data::Enum(enm) => derive_try_from_bytes_enum(ctx, enm, top_level),
450 Data::Union(unn) => Ok(derive_try_from_bytes_union(ctx, unn, top_level)),
451 }
452}
453fn derive_has_field_struct_union(ctx: &Ctx, data: &dyn DataExt) -> TokenStream {
454 let fields = ctx.ast.data.fields();
455 if fields.is_empty() {
456 return quote! {};
457 }
458
459 let field_tokens = fields.iter().map(|(vis, ident, _)| {
460 let ident = ident!(("__z{}", ident), ident.span());
461 quote!(
462 #vis enum #ident {}
463 )
464 });
465
466 let zerocopy_crate = &ctx.zerocopy_crate;
467 let variant_id: Box<Expr> = match &ctx.ast.data {
468 Data::Struct(_) => parse_quote!({ #zerocopy_crate::STRUCT_VARIANT_ID }),
469 Data::Union(_) => {
470 let is_repr_c = StructUnionRepr::from_attrs(&ctx.ast.attrs)
471 .map(|repr| repr.is_c())
472 .unwrap_or(false);
473 if is_repr_c {
474 parse_quote!({ #zerocopy_crate::REPR_C_UNION_VARIANT_ID })
475 } else {
476 parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID })
477 }
478 }
479 _ => unreachable!(),
480 };
481
482 let core = ctx.core_path();
483 let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag, FieldBounds::None)
484 .inner_extras(quote! {
485 type Tag = ();
486 type ProjectToTag = #zerocopy_crate::pointer::cast::CastToUnit;
487 })
488 .build();
489 let has_fields = fields.iter().map(move |(_, ident, ty)| {
490 let field_token = ident!(("__z{}", ident), ident.span());
491 let field: Box<Type> = parse_quote!(#field_token);
492 let field_id: Box<Expr> = parse_quote!({ #zerocopy_crate::ident_id!(#ident) });
493 let has_field_trait = Trait::HasField {
494 variant_id: variant_id.clone(),
495 field: field.clone(),
496 field_id: field_id.clone(),
497 };
498 let has_field_path = has_field_trait.crate_path(ctx);
499 ImplBlockBuilder::new(
500 ctx,
501 data,
502 has_field_trait,
503 FieldBounds::None,
504 )
505 .inner_extras(quote! {
506 type Type = #ty;
507
508 #[inline(always)]
509 fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut <Self as #has_field_path>::Type {
510 let slf = slf.as_ptr();
511 unsafe { #core::ptr::addr_of_mut!((*slf).#ident) }
518 }
519 }).outer_extras(if matches!(&ctx.ast.data, Data::Struct(..)) {
520 let fields_preserve_alignment = StructUnionRepr::from_attrs(&ctx.ast.attrs)
521 .map(|repr| repr.get_packed().is_none())
522 .unwrap();
523 let alignment = if fields_preserve_alignment {
524 quote! { Alignment }
525 } else {
526 quote! { #zerocopy_crate::invariant::Unaligned }
527 };
528 ImplBlockBuilder::new(
530 ctx,
531 data,
532 Trait::ProjectField {
533 variant_id: variant_id.clone(),
534 field: field.clone(),
535 field_id: field_id.clone(),
536 invariants: parse_quote!((Aliasing, Alignment, #zerocopy_crate::invariant::Initialized)),
537 },
538 FieldBounds::None,
539 )
540 .param_extras(vec![
541 parse_quote!(Aliasing: #zerocopy_crate::invariant::Aliasing),
542 parse_quote!(Alignment: #zerocopy_crate::invariant::Alignment),
543 ])
544 .inner_extras(quote! {
545 type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
547 type Invariants = (Aliasing, #alignment, #zerocopy_crate::invariant::Initialized);
552 })
553 .build()
554 } else {
555 quote! {}
556 })
557 .build()
558 });
559
560 const_block(field_tokens.into_iter().chain(Some(has_tag)).chain(has_fields).map(Some))
561}
562fn derive_try_from_bytes_struct(
563 ctx: &Ctx,
564 strct: &DataStruct,
565 top_level: Trait,
566) -> Result<TokenStream, Error> {
567 let extras = try_gen_trivial_is_bit_valid(ctx, top_level).unwrap_or_else(|| {
568 let zerocopy_crate = &ctx.zerocopy_crate;
569 let fields = strct.fields();
570 let field_names = fields.iter().map(|(_vis, name, _ty)| name);
571 let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
572 let core = ctx.core_path();
573 quote!(
574 #[inline]
580 fn is_bit_valid<___ZcAlignment>(
581 mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
582 ) -> #core::primitive::bool
583 where
584 ___ZcAlignment: #zerocopy_crate::invariant::Alignment,
585 {
586 true #(&& {
587 let field_candidate = #zerocopy_crate::into_inner!(candidate.reborrow().project::<
588 _,
589 { #zerocopy_crate::STRUCT_VARIANT_ID },
590 { #zerocopy_crate::ident_id!(#field_names) }
591 >());
592 <#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
593 })*
594 }
595 )
596 });
597 Ok(ImplBlockBuilder::new(ctx, strct, Trait::TryFromBytes, FieldBounds::ALL_SELF)
598 .inner_extras(extras)
599 .outer_extras(derive_has_field_struct_union(ctx, strct))
600 .build())
601}
602fn derive_try_from_bytes_union(ctx: &Ctx, unn: &DataUnion, top_level: Trait) -> TokenStream {
603 let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
604
605 let zerocopy_crate = &ctx.zerocopy_crate;
606 let variant_id: Box<Expr> = {
607 let is_repr_c =
608 StructUnionRepr::from_attrs(&ctx.ast.attrs).map(|repr| repr.is_c()).unwrap_or(false);
609 if is_repr_c {
610 parse_quote!({ #zerocopy_crate::REPR_C_UNION_VARIANT_ID })
611 } else {
612 parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID })
613 }
614 };
615
616 let extras = try_gen_trivial_is_bit_valid(ctx, top_level).unwrap_or_else(|| {
617 let fields = unn.fields();
618 let field_names = fields.iter().map(|(_vis, name, _ty)| name);
619 let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
620 let core = ctx.core_path();
621 quote!(
622 #[inline]
628 fn is_bit_valid<___ZcAlignment>(
629 mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
630 ) -> #core::primitive::bool
631 where
632 ___ZcAlignment: #zerocopy_crate::invariant::Alignment,
633 {
634 false #(|| {
635 let field_candidate = unsafe {
643 candidate.reborrow().project_transmute_unchecked::<
644 _,
645 _,
646 #zerocopy_crate::pointer::cast::Projection<
647 _,
648 #variant_id,
649 { #zerocopy_crate::ident_id!(#field_names) }
650 >
651 >()
652 };
653
654 <#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
655 })*
656 }
657 )
658 });
659 ImplBlockBuilder::new(ctx, unn, Trait::TryFromBytes, field_type_trait_bounds)
660 .inner_extras(extras)
661 .outer_extras(derive_has_field_struct_union(ctx, unn))
662 .build()
663}
664fn derive_try_from_bytes_enum(
665 ctx: &Ctx,
666 enm: &DataEnum,
667 top_level: Trait,
668) -> Result<TokenStream, Error> {
669 let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
670
671 let could_be_from_bytes = enum_size_from_repr(&repr)
677 .map(|size| enm.fields().is_empty() && enm.variants.len() == 1usize << size)
678 .unwrap_or(false);
679
680 let trivial_is_bit_valid = try_gen_trivial_is_bit_valid(ctx, top_level);
681 let extra = match (trivial_is_bit_valid, could_be_from_bytes) {
682 (Some(is_bit_valid), _) => is_bit_valid,
683 (None, true) => unsafe { gen_trivial_is_bit_valid_unchecked(ctx) },
686 (None, false) => match derive_is_bit_valid(ctx, enm, &repr) {
687 Ok(extra) => extra,
688 Err(_) if ctx.skip_on_error => return Ok(TokenStream::new()),
689 Err(e) => return Err(e),
690 },
691 };
692
693 Ok(ImplBlockBuilder::new(ctx, enm, Trait::TryFromBytes, FieldBounds::ALL_SELF)
694 .inner_extras(extra)
695 .build())
696}
697fn try_gen_trivial_is_bit_valid(ctx: &Ctx, top_level: Trait) -> Option<proc_macro2::TokenStream> {
698 if matches!(top_level, Trait::FromBytes)
710 && ctx.ast.generics.params.is_empty()
711 && !ctx.skip_on_error
712 {
713 let zerocopy_crate = &ctx.zerocopy_crate;
714 let core = ctx.core_path();
715 Some(quote!(
716 #[inline(always)]
718 fn is_bit_valid<___ZcAlignment>(
719 _candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
720 ) -> #core::primitive::bool
721 where
722 ___ZcAlignment: #zerocopy_crate::invariant::Alignment,
723 {
724 if false {
725 fn assert_is_from_bytes<T>()
726 where
727 T: #zerocopy_crate::FromBytes,
728 T: ?#core::marker::Sized,
729 {
730 }
731
732 assert_is_from_bytes::<Self>();
733 }
734
735 true
739 }
740 ))
741 } else {
742 None
743 }
744}
745
746unsafe fn gen_trivial_is_bit_valid_unchecked(ctx: &Ctx) -> proc_macro2::TokenStream {
750 let zerocopy_crate = &ctx.zerocopy_crate;
751 let core = ctx.core_path();
752 quote!(
753 #[inline(always)]
756 fn is_bit_valid<___ZcAlignment>(
757 _candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
758 ) -> #core::primitive::bool
759 where
760 ___ZcAlignment: #zerocopy_crate::invariant::Alignment,
761 {
762 true
763 }
764 )
765}