Skip to main content

zerocopy_derive/derive/
try_from_bytes.rs

1// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
2//
3use 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    // Choose a source-pointer name distinct from the field names. Mixed-site
21    // hygiene keeps it out of the user's invariant expressions.
22    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
30/// Generates validation of every field of a struct or enum variant, leaving
31/// the validated pointers in scope for subsequent field invariants.
32/// A union is checked by invoking this once per field.
33fn 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    // Without invariants, no expression needs the validated pointers. Avoid
68    // introducing bindings that could collide with caller-defined items.
69    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                    // Keep `return` in an invariant from bypassing later fields.
91                    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                // Retain each shared pointer for subsequent field invariants.
102                #[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
114/// Generates an implementation of `is_safe` for an arbitrary enum.
115///
116/// For an enum with fields, [`derive_enum`] generates the representation model
117/// and projection impls. This function reads the tag, matches it against the
118/// enum's discriminants, and validates each field of the selected variant
119/// through those projections. A fieldless enum needs only the generated tag
120/// enum and discriminant constants.
121pub(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            // Keep implementation-only items and their lint allowances out
192            // of the scopes containing caller-authored invariant expressions.
193            #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        // SAFETY: We use `is_safe` to validate that the bit pattern of the
209        // enum's tag corresponds to one of the enum's discriminants. Then, we
210        // check the bit validity of each field of the corresponding variant.
211        // Thus, this is a sound implementation of `is_safe`.
212        #[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            // SAFETY: We use `is_safe` to validate that each field is bit-valid,
251            // and only return `true` if all of them are. The bit validity of a
252            // struct is just the composition of the bit validities of its
253            // fields, so this is a sound implementation of `is_safe`.
254            #[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            // SAFETY: We use `is_safe` to validate that any field is bit-valid;
293            // we only return `true` if at least one of them is and its
294            // invariants also hold. The bit validity of a union is not yet
295            // well defined in Rust, but it is guaranteed to be no more strict
296            // than this definition. See #696 for a more in-depth discussion.
297            #[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                // Keep each field's bindings and early returns local to that
305                // field, so a failure lets validation try the next field.
306                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    // If an enum has no fields, it has a well-defined integer representation,
323    // and every possible bit pattern corresponds to a valid discriminant tag,
324    // then it *could* be `FromBytes` (even if the user hasn't derived
325    // `FromBytes`). This holds if, for `repr(uN)` or `repr(iN)`, there are 2^N
326    // variants.
327    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        // SAFETY: It would be sound for the enum to implement `FromBytes`, as
335        // required by `gen_trivial_is_safe_unchecked`.
336        (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 the top-level trait is `FromBytes` and `Self` has no type parameters,
349    // then the `FromBytes` derive will fail compilation if `Self` is not
350    // actually soundly `FromBytes`, and so we can rely on that for our `is_safe`
351    // impl. It's plausible that we could make changes - or Rust could make
352    // changes (such as the "trivial bounds" language feature) - that make this
353    // no longer true. To hedge against these, we include an explicit `Self:
354    // FromBytes` check in the generated `is_safe`, which is bulletproof.
355    //
356    // If `ctx.skip_on_error` is true, we can't rely on the `FromBytes` derive
357    // to fail compilation if `Self` is not actually soundly `FromBytes`.
358    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            // SAFETY: See inline.
366            #[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                // SAFETY: The preceding code only compiles if `Self:
385                // FromBytes`. Thus, this code only compiles if all initialized
386                // byte sequences represent valid instances of `Self`.
387                true
388            }
389        ))
390    } else {
391        None
392    }
393}
394
395/// # Safety
396///
397/// All initialized bit patterns must be valid for `Self`.
398unsafe 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        // SAFETY: The caller of `gen_trivial_is_safe_unchecked` has promised
403        // that all initialized bit patterns are valid for `Self`.
404        #[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        // Rejecting both spellings prevents generating a validator whose
450        // synthetic enum layout disagrees with the compiler's actual layout.
451        assert_eq!(ordinary, "this conflicts with another representation hint");
452        assert_eq!(ordinary, raw);
453    }
454}