1use proc_macro2::{Ident, Span, TokenStream};
4use quote::{quote, ToTokens as _};
5use syn::{spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, Error, Field};
6
7use crate::{
8 derive::project::{
9 derive_enum, derive_projection_struct_union, generate_tag_consts, struct_union_variant_id,
10 tag_ident,
11 },
12 repr::EnumRepr,
13 util::{
14 enum_size_from_repr, generate_tag_enum, Client, Ctx, DataExt, FieldBounds,
15 ImplBlockBuilder, Trait, TraitBound,
16 },
17};
18
19fn candidate_ident(ctx: &Ctx) -> Ident {
20 let fields = ctx.ast.data.fields();
23 let mut name = "candidate".to_owned();
24 while fields.iter().any(|(_, field, _)| field.to_string().trim_start_matches("r#") == name) {
25 name.push('_');
26 }
27 Ident::new(&name, Span::mixed_site())
28}
29
30fn derive_variant_is_safe<'a>(
34 ctx: &Ctx,
35 variant_id: &TokenStream,
36 fields: impl IntoIterator<Item = &'a Field>,
37) -> Result<TokenStream, Error> {
38 let fields = fields.into_iter().collect::<Vec<_>>();
39 if fields.is_empty() {
40 return Ok(quote!(true));
41 }
42
43 let zerocopy_crate = &ctx.zerocopy_crate;
44 let core = ctx.core_path();
45 let candidate = candidate_ident(ctx);
46 let field_names = fields.iter().enumerate().map(|(idx, field)| {
47 field
48 .ident
49 .as_ref()
50 .map(|name| name.to_token_stream())
51 .unwrap_or_else(|| syn::Index::from(idx).to_token_stream())
52 });
53 let field_checks = field_names
54 .map(|name| {
55 quote! {
56 #zerocopy_crate::into_inner!(
57 #candidate.project::<
58 #zerocopy_crate::project_clients::TryFromBytesDerive,
59 _,
60 { #variant_id },
61 { #zerocopy_crate::ident_id!(#name) },
62 >()
63 ).try_into_safe::<_, #zerocopy_crate::BecauseImmutable>()
64 }
65 })
66 .collect::<Vec<_>>();
67 if ctx.invariant_span.is_none() {
70 return Ok(quote! { true #(&& #field_checks.is_ok())* });
71 }
72
73 let field_bindings = fields
74 .iter()
75 .enumerate()
76 .map(|(idx, _)| ctx.fresh_ident(&format!("field_{}", idx)))
77 .collect::<Vec<_>>();
78 let bound_fields =
79 fields.iter().copied().zip(field_bindings.iter().cloned()).collect::<Vec<_>>();
80 let field_validations = fields
81 .iter()
82 .enumerate()
83 .map(|(idx, field)| {
84 let invariants = crate::invariant::parse(&field.attrs)?;
85 let invariants = invariants
86 .iter()
87 .map(|expression| crate::invariant::bind_fields(&bound_fields[..=idx], expression));
88 Ok(quote! {
89 #(
90 if !{ #[inline(always)] || -> #core::primitive::bool { #invariants } }() {
92 return false;
93 }
94 )*
95 })
96 })
97 .collect::<Result<Vec<_>, Error>>()?;
98 Ok(quote! {
99 {
100 #(
101 #[allow(unused_variables)]
103 let #field_bindings = match #field_checks {
104 #core::result::Result::Ok(#field_bindings) => #field_bindings,
105 #core::result::Result::Err(_) => return false,
106 };
107 #field_validations
108 )*
109 true
110 }
111 })
112}
113
114pub(crate) fn derive_is_safe(
122 ctx: &Ctx,
123 data: &DataEnum,
124 repr: &EnumRepr,
125) -> Result<TokenStream, Error> {
126 if !(repr.is_c() || repr.is_primitive()) {
127 return Err(Error::new(
128 ctx.ast.span(),
129 "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
130 ));
131 }
132
133 let zerocopy_crate = &ctx.zerocopy_crate;
134 let core = ctx.core_path();
135 let candidate = candidate_ident(ctx);
136 let alignment = ctx.fresh_ident("___ZcAlignment");
137 let tag = Ident::new("tag", Span::mixed_site());
138 let projections = if data.fields().is_empty() {
139 let tag_enum = generate_tag_enum(ctx, repr, data);
140 let tag_consts = generate_tag_consts(data);
141 quote! {
142 #tag_enum
143
144 type ___ZerocopyTagPrimitive = #zerocopy_crate::util::macro_util::SizeToTag<
145 { #core::mem::size_of::<___ZerocopyTag>() },
146 >;
147
148 #tag_consts
149 }
150 } else {
151 derive_enum(ctx, data, Client::TryFromBytesDerive)?
152 };
153
154 let match_arms = data
155 .variants
156 .iter()
157 .enumerate()
158 .map(|(idx, variant)| {
159 let name = &variant.ident;
160 let variant_id = quote! { #zerocopy_crate::ident_id!(#name) };
161 let fields_is_safe = derive_variant_is_safe(ctx, &variant_id, &variant.fields)?;
162 let pattern = if ctx.invariant_span.is_some() {
163 quote! { #core::option::Option::Some(#idx) }
164 } else {
165 tag_ident(name).to_token_stream()
166 };
167 Ok(quote! {
168 #pattern => { #fields_is_safe }
169 })
170 })
171 .collect::<Result<Vec<_>, Error>>()?;
172
173 let read_tag = quote! {
174 let #tag = #candidate
175 .reborrow()
176 .cast::<
177 ___ZerocopyTagPrimitive,
178 #zerocopy_crate::pointer::cast::CastSized,
179 (#zerocopy_crate::pointer::BecauseRead, _),
180 >()
181 .recall_validity::<_, (_, (_, _))>()
182 .read::<#zerocopy_crate::BecauseImmutable>();
183 };
184 let tag_init = if ctx.invariant_span.is_some() {
185 let allow = crate::util::allow_generated_code();
186 let tag_arms = data.variants.iter().enumerate().map(|(idx, variant)| {
187 let tag = tag_ident(&variant.ident);
188 quote! { #tag => #core::option::Option::Some(#idx) }
189 });
190 quote! {
191 #allow
194 let #tag = {
195 #projections
196 #read_tag
197 match #tag {
198 #(#tag_arms,)*
199 _ => #core::option::Option::None,
200 }
201 };
202 }
203 } else {
204 quote! { #projections #read_tag }
205 };
206
207 Ok(quote! {
208 #[inline]
213 fn is_safe<#alignment>(
214 mut #candidate: #zerocopy_crate::Maybe<'_, Self, #alignment>,
215 ) -> #core::primitive::bool
216 where
217 #alignment: #zerocopy_crate::invariant::Alignment,
218 {
219 #tag_init
220
221 match #tag {
222 #(#match_arms,)*
223 _ => false,
224 }
225 }
226 })
227}
228pub(crate) fn derive_try_from_bytes(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
229 match &ctx.ast.data {
230 Data::Struct(strct) => derive_try_from_bytes_struct(ctx, strct, top_level),
231 Data::Enum(enm) => derive_try_from_bytes_enum(ctx, enm, top_level),
232 Data::Union(unn) => derive_try_from_bytes_union(ctx, unn, top_level),
233 }
234}
235fn derive_try_from_bytes_struct(
236 ctx: &Ctx,
237 strct: &DataStruct,
238 top_level: Trait,
239) -> Result<TokenStream, Error> {
240 let extras = if let Some(extras) = try_gen_trivial_is_safe(ctx, top_level) {
241 extras
242 } else {
243 let zerocopy_crate = &ctx.zerocopy_crate;
244 let variant_id = quote! { #zerocopy_crate::STRUCT_VARIANT_ID };
245 let fields_is_safe = derive_variant_is_safe(ctx, &variant_id, &strct.fields)?;
246 let core = ctx.core_path();
247 let candidate = candidate_ident(ctx);
248 let alignment = ctx.fresh_ident("___ZcAlignment");
249 quote!(
250 #[inline]
255 fn is_safe<#alignment>(
256 mut #candidate: #zerocopy_crate::Maybe<'_, Self, #alignment>,
257 ) -> #core::primitive::bool
258 where
259 #alignment: #zerocopy_crate::invariant::Alignment,
260 {
261 #fields_is_safe
262 }
263 )
264 };
265 Ok(ImplBlockBuilder::new(ctx, strct, Trait::TryFromBytes, FieldBounds::ALL_SELF)
266 .inner_extras(extras)
267 .outer_extras(derive_projection_struct_union(ctx, strct, Client::TryFromBytesDerive))
268 .build())
269}
270fn derive_try_from_bytes_union(
271 ctx: &Ctx,
272 unn: &DataUnion,
273 top_level: Trait,
274) -> Result<TokenStream, Error> {
275 let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
276
277 let zerocopy_crate = &ctx.zerocopy_crate;
278 let union_variant_id = struct_union_variant_id(ctx).to_token_stream();
279 let extras = if let Some(extras) = try_gen_trivial_is_safe(ctx, top_level) {
280 extras
281 } else {
282 let fields_is_safe = unn
283 .fields
284 .named
285 .iter()
286 .map(|field| derive_variant_is_safe(ctx, &union_variant_id, [field]))
287 .collect::<Result<Vec<_>, _>>()?;
288 let core = ctx.core_path();
289 let candidate = candidate_ident(ctx);
290 let alignment = ctx.fresh_ident("___ZcAlignment");
291 quote!(
292 #[inline]
298 fn is_safe<#alignment>(
299 mut #candidate: #zerocopy_crate::Maybe<'_, Self, #alignment>,
300 ) -> #core::primitive::bool
301 where
302 #alignment: #zerocopy_crate::invariant::Alignment,
303 {
304 false #(|| { #[inline(always)] || { #fields_is_safe } }())*
307 }
308 )
309 };
310 Ok(ImplBlockBuilder::new(ctx, unn, Trait::TryFromBytes, field_type_trait_bounds)
311 .inner_extras(extras)
312 .outer_extras(derive_projection_struct_union(ctx, unn, Client::TryFromBytesDerive))
313 .build())
314}
315fn derive_try_from_bytes_enum(
316 ctx: &Ctx,
317 enm: &DataEnum,
318 top_level: Trait,
319) -> Result<TokenStream, Error> {
320 let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
321
322 let could_be_from_bytes = enum_size_from_repr(&repr)
328 .map(|size| enm.fields().is_empty() && enm.variants.len() == 1usize << size)
329 .unwrap_or(false);
330
331 let trivial_is_safe = try_gen_trivial_is_safe(ctx, top_level);
332 let extra = match (trivial_is_safe, could_be_from_bytes) {
333 (Some(is_safe), _) => is_safe,
334 (None, true) => unsafe { gen_trivial_is_safe_unchecked(ctx) },
337 (None, false) => match derive_is_safe(ctx, enm, &repr) {
338 Ok(extra) => extra,
339 Err(e) => return ctx.error_or_skip(e),
340 },
341 };
342
343 Ok(ImplBlockBuilder::new(ctx, enm, Trait::TryFromBytes, FieldBounds::ALL_SELF)
344 .inner_extras(extra)
345 .build())
346}
347fn try_gen_trivial_is_safe(ctx: &Ctx, top_level: Trait) -> Option<proc_macro2::TokenStream> {
348 if matches!(top_level, Trait::FromBytes)
359 && ctx.ast.generics.params.is_empty()
360 && !ctx.skip_on_error
361 {
362 let zerocopy_crate = &ctx.zerocopy_crate;
363 let core = ctx.core_path();
364 Some(quote!(
365 #[inline(always)]
367 fn is_safe<___ZcAlignment>(
368 _candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
369 ) -> #core::primitive::bool
370 where
371 ___ZcAlignment: #zerocopy_crate::invariant::Alignment,
372 {
373 if false {
374 fn assert_is_from_bytes<T>()
375 where
376 T: #zerocopy_crate::FromBytes,
377 T: ?#core::marker::Sized,
378 {
379 }
380
381 assert_is_from_bytes::<Self>();
382 }
383
384 true
388 }
389 ))
390 } else {
391 None
392 }
393}
394
395unsafe fn gen_trivial_is_safe_unchecked(ctx: &Ctx) -> proc_macro2::TokenStream {
399 let zerocopy_crate = &ctx.zerocopy_crate;
400 let core = ctx.core_path();
401 quote!(
402 #[inline(always)]
405 fn is_safe<___ZcAlignment>(
406 _candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
407 ) -> #core::primitive::bool
408 where
409 ___ZcAlignment: #zerocopy_crate::invariant::Alignment,
410 {
411 true
412 }
413 )
414}
415
416#[cfg(test)]
417mod tests {
418 use syn::{parse_quote, DeriveInput};
419
420 use super::*;
421
422 fn derive_error(input: DeriveInput) -> String {
423 let ctx = Ctx::try_from_derive_input(input).unwrap();
424 match derive_try_from_bytes(&ctx, Trait::TryFromBytes) {
425 Ok(_) => panic!("expected derive to reject conflicting representations"),
426 Err(error) => error.to_string(),
427 }
428 }
429
430 #[test]
431 fn raw_repr_conflict_matches_ordinary_repr() {
432 let ordinary = derive_error(parse_quote! {
433 #[repr(C, align(8))]
434 #[repr(u8)]
435 enum Packet {
436 Value(core::num::NonZeroU8),
437 End,
438 }
439 });
440 let raw = derive_error(parse_quote! {
441 #[repr(C, align(8))]
442 #[r#repr(r#u8)]
443 enum Packet {
444 Value(core::num::NonZeroU8),
445 End,
446 }
447 });
448
449 assert_eq!(ordinary, "this conflicts with another representation hint");
452 assert_eq!(ordinary, raw);
453 }
454}