xref: /linux/rust/zerocopy-derive/derive/from_bytes.rs (revision 7c306f00835a237a70024a8f968dbc3bc8db8fcf)
1 // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
2 //
3 use proc_macro2::{Span, TokenStream};
4 use syn::{
5     parse_quote, Data, DataEnum, DataStruct, DataUnion, Error, Expr, ExprLit, ExprUnary, Lit, UnOp,
6     WherePredicate,
7 };
8 
9 use crate::{
10     derive::try_from_bytes::derive_try_from_bytes,
11     repr::{CompoundRepr, EnumRepr, Repr, Spanned},
12     util::{enum_size_from_repr, Ctx, FieldBounds, ImplBlockBuilder, Trait, TraitBound},
13 };
14 /// Returns `Ok(index)` if variant `index` of the enum has a discriminant of
15 /// zero. If `Err(bool)` is returned, the boolean is true if the enum has
16 /// unknown discriminants (e.g. discriminants set to const expressions which we
17 /// can't evaluate in a proc macro). If the enum has unknown discriminants, then
18 /// it might have a zero variant that we just can't detect.
19 pub(crate) fn find_zero_variant(enm: &DataEnum) -> Result<usize, bool> {
20     // Discriminants can be anywhere in the range [i128::MIN, u128::MAX] because
21     // the discriminant type may be signed or unsigned. Since we only care about
22     // tracking the discriminant when it's less than or equal to zero, we can
23     // avoid u128 -> i128 conversions and bounds checking by making the "next
24     // discriminant" value implicitly negative.
25     // Technically 64 bits is enough, but 128 is better for future compatibility
26     // with https://github.com/rust-lang/rust/issues/56071
27     let mut next_negative_discriminant = Some(0);
28 
29     // Sometimes we encounter explicit discriminants that we can't know the
30     // value of (e.g. a constant expression that requires evaluation). These
31     // could evaluate to zero or a negative number, but we can't assume that
32     // they do (no false positives allowed!). So we treat them like strictly-
33     // positive values that can't result in any zero variants, and track whether
34     // we've encountered any unknown discriminants.
35     let mut has_unknown_discriminants = false;
36 
37     for (i, v) in enm.variants.iter().enumerate() {
38         match v.discriminant.as_ref() {
39             // Implicit discriminant
40             None => {
41                 match next_negative_discriminant.as_mut() {
42                     Some(0) => return Ok(i),
43                     // n is nonzero so subtraction is always safe
44                     Some(n) => *n -= 1,
45                     None => (),
46                 }
47             }
48             // Explicit positive discriminant
49             Some((_, Expr::Lit(ExprLit { lit: Lit::Int(int), .. }))) => {
50                 match int.base10_parse::<u128>().ok() {
51                     Some(0) => return Ok(i),
52                     Some(_) => next_negative_discriminant = None,
53                     None => {
54                         // Numbers should never fail to parse, but just in case:
55                         has_unknown_discriminants = true;
56                         next_negative_discriminant = None;
57                     }
58                 }
59             }
60             // Explicit negative discriminant
61             Some((_, Expr::Unary(ExprUnary { op: UnOp::Neg(_), expr, .. }))) => match &**expr {
62                 Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) => {
63                     match int.base10_parse::<u128>().ok() {
64                         Some(0) => return Ok(i),
65                         // x is nonzero so subtraction is always safe
66                         Some(x) => next_negative_discriminant = Some(x - 1),
67                         None => {
68                             // Numbers should never fail to parse, but just in
69                             // case:
70                             has_unknown_discriminants = true;
71                             next_negative_discriminant = None;
72                         }
73                     }
74                 }
75                 // Unknown negative discriminant (e.g. const repr)
76                 _ => {
77                     has_unknown_discriminants = true;
78                     next_negative_discriminant = None;
79                 }
80             },
81             // Unknown discriminant (e.g. const expr)
82             _ => {
83                 has_unknown_discriminants = true;
84                 next_negative_discriminant = None;
85             }
86         }
87     }
88 
89     Err(has_unknown_discriminants)
90 }
91 pub(crate) fn derive_from_zeros(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
92     if let Some(span) = ctx.invariant_span {
93         return ctx.error_or_skip(Error::new(
94             span,
95             "cannot derive `FromZeros` for a type with invariants",
96         ));
97     }
98     let try_from_bytes = derive_try_from_bytes(ctx, top_level)?;
99     let from_zeros = match &ctx.ast.data {
100         Data::Struct(strct) => derive_from_zeros_struct(ctx, strct),
101         Data::Enum(enm) => derive_from_zeros_enum(ctx, enm)?,
102         Data::Union(unn) => derive_from_zeros_union(ctx, unn),
103     };
104     Ok(IntoIterator::into_iter([try_from_bytes, from_zeros]).collect())
105 }
106 pub(crate) fn derive_from_bytes(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
107     if let Some(span) = ctx.invariant_span {
108         return ctx.error_or_skip(Error::new(
109             span,
110             "cannot derive `FromBytes` for a type with invariants",
111         ));
112     }
113     let from_zeros = derive_from_zeros(ctx, top_level)?;
114     let from_bytes = match &ctx.ast.data {
115         Data::Struct(strct) => derive_from_bytes_struct(ctx, strct),
116         Data::Enum(enm) => derive_from_bytes_enum(ctx, enm)?,
117         Data::Union(unn) => derive_from_bytes_union(ctx, unn),
118     };
119 
120     Ok(IntoIterator::into_iter([from_zeros, from_bytes]).collect())
121 }
122 fn derive_from_zeros_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream {
123     ImplBlockBuilder::new(ctx, strct, Trait::FromZeros, FieldBounds::ALL_SELF).build()
124 }
125 fn derive_from_zeros_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
126     let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
127 
128     // We don't actually care what the repr is; we just care that it's one of
129     // the allowed ones.
130     match repr {
131         Repr::Compound(Spanned { t: CompoundRepr::C | CompoundRepr::Primitive(_), span: _ }, _) => {
132         }
133         Repr::Transparent(_) | Repr::Compound(Spanned { t: CompoundRepr::Rust, span: _ }, _) => {
134             return ctx.error_or_skip(
135                 Error::new(
136                     Span::call_site(),
137                     "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
138                 ),
139             );
140         }
141     }
142 
143     let zero_variant = match find_zero_variant(enm) {
144         Ok(index) => enm.variants.iter().nth(index).unwrap(),
145         // Has unknown variants
146         Err(true) => {
147             return ctx.error_or_skip(Error::new_spanned(
148                 &ctx.ast,
149                 "FromZeros only supported on enums with a variant that has a discriminant of `0`\n\
150                 help: This enum has discriminants which are not literal integers. One of those may \
151                 define or imply which variant has a discriminant of zero. Use a literal integer to \
152                 define or imply the variant with a discriminant of zero.",
153             ));
154         }
155         // Does not have unknown variants
156         Err(false) => {
157             return ctx.error_or_skip(Error::new_spanned(
158                 &ctx.ast,
159                 "FromZeros only supported on enums with a variant that has a discriminant of `0`",
160             ));
161         }
162     };
163 
164     let zerocopy_crate = &ctx.zerocopy_crate;
165     let explicit_bounds = zero_variant
166         .fields
167         .iter()
168         .map(|field| {
169             let ty = &field.ty;
170             parse_quote! { #ty: #zerocopy_crate::FromZeros }
171         })
172         .collect::<Vec<WherePredicate>>();
173 
174     Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromZeros, FieldBounds::Explicit(explicit_bounds))
175         .build())
176 }
177 fn derive_from_zeros_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream {
178     let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
179     ImplBlockBuilder::new(ctx, unn, Trait::FromZeros, field_type_trait_bounds).build()
180 }
181 fn derive_from_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream {
182     ImplBlockBuilder::new(ctx, strct, Trait::FromBytes, FieldBounds::ALL_SELF).build()
183 }
184 fn derive_from_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
185     let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
186 
187     let variants_required = 1usize << enum_size_from_repr(&repr)?;
188     if enm.variants.len() != variants_required {
189         return ctx.error_or_skip(Error::new_spanned(
190             &ctx.ast,
191             format!(
192                 "FromBytes only supported on {} enum with {} variants",
193                 repr.repr_type_name(),
194                 variants_required
195             ),
196         ));
197     }
198 
199     Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromBytes, FieldBounds::ALL_SELF).build())
200 }
201 fn derive_from_bytes_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream {
202     let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
203     ImplBlockBuilder::new(ctx, unn, Trait::FromBytes, field_type_trait_bounds).build()
204 }
205