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