zerocopy_derive/
repr.rs

1// Copyright 2019 The Fuchsia Authors
2//
3// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6// This file may not be copied, modified, or distributed except according to
7// those terms.
8
9use core::fmt::{self, Display, Formatter};
10
11use {
12    proc_macro2::Span,
13    syn::punctuated::Punctuated,
14    syn::spanned::Spanned,
15    syn::token::Comma,
16    syn::{Attribute, DeriveInput, Error, LitInt, Meta},
17};
18
19pub struct Config<Repr: KindRepr> {
20    // A human-readable message describing what combinations of representations
21    // are allowed. This will be printed to the user if they use an invalid
22    // combination.
23    pub allowed_combinations_message: &'static str,
24    // Whether we're checking as part of `derive(Unaligned)`. If not, we can
25    // ignore `repr(align)`, which makes the code (and the list of valid repr
26    // combinations we have to enumerate) somewhat simpler. If we're checking
27    // for `Unaligned`, then in addition to checking against illegal
28    // combinations, we also check to see if there exists a `repr(align(N > 1))`
29    // attribute.
30    pub derive_unaligned: bool,
31    // Combinations which are valid for the trait.
32    pub allowed_combinations: &'static [&'static [Repr]],
33    // Combinations which are not valid for the trait, but are legal according
34    // to Rust. Any combination not in this or `allowed_combinations` is either
35    // illegal according to Rust or the behavior is unspecified. If the behavior
36    // is unspecified, it might become specified in the future, and that
37    // specification might not play nicely with our requirements. Thus, we
38    // reject combinations with unspecified behavior in addition to illegal
39    // combinations.
40    pub disallowed_but_legal_combinations: &'static [&'static [Repr]],
41}
42
43impl<R: KindRepr> Config<R> {
44    /// Validate that `input`'s representation attributes conform to the
45    /// requirements specified by this `Config`.
46    ///
47    /// `validate_reprs` extracts the `repr` attributes, validates that they
48    /// conform to the requirements of `self`, and returns them. Regardless of
49    /// whether `align` attributes are considered during validation, they are
50    /// stripped out of the returned value since no callers care about them.
51    pub fn validate_reprs(&self, input: &DeriveInput) -> Result<Vec<R>, Vec<Error>> {
52        let mut metas_reprs = reprs(&input.attrs)?;
53        metas_reprs.sort_by(|a: &(_, R), b| a.1.partial_cmp(&b.1).unwrap());
54
55        if self.derive_unaligned {
56            if let Some((meta, _)) =
57                metas_reprs.iter().find(|&repr: &&(_, R)| repr.1.is_align_gt_one())
58            {
59                return Err(vec![Error::new_spanned(
60                    meta,
61                    "cannot derive Unaligned with repr(align(N > 1))",
62                )]);
63            }
64        }
65
66        let mut metas = Vec::new();
67        let mut reprs = Vec::new();
68        metas_reprs.into_iter().filter(|(_, repr)| !repr.is_align()).for_each(|(meta, repr)| {
69            metas.push(meta);
70            reprs.push(repr)
71        });
72
73        if reprs.is_empty() {
74            // Use `Span::call_site` to report this error on the
75            // `#[derive(...)]` itself.
76            return Err(vec![Error::new(Span::call_site(), "must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout")]);
77        }
78
79        let initial_sp = metas[0].span();
80        let err_span = metas.iter().skip(1).try_fold(initial_sp, |sp, meta| sp.join(meta.span()));
81
82        if self.allowed_combinations.contains(&reprs.as_slice()) {
83            Ok(reprs)
84        } else if self.disallowed_but_legal_combinations.contains(&reprs.as_slice()) {
85            Err(vec![Error::new(
86                err_span.unwrap_or_else(|| input.span()),
87                self.allowed_combinations_message,
88            )])
89        } else {
90            Err(vec![Error::new(
91                err_span.unwrap_or_else(|| input.span()),
92                "conflicting representation hints",
93            )])
94        }
95    }
96}
97
98// The type of valid reprs for a particular kind (enum, struct, union).
99pub trait KindRepr: 'static + Sized + Ord {
100    fn is_align(&self) -> bool;
101    fn is_align_gt_one(&self) -> bool;
102    fn parse(meta: &Meta) -> syn::Result<Self>;
103}
104
105// Defines an enum for reprs which are valid for a given kind (structs, enums,
106// etc), and provide implementations of `KindRepr`, `Ord`, and `Display`, and
107// those traits' super-traits.
108macro_rules! define_kind_specific_repr {
109    ($type_name:expr, $repr_name:ident, [ $($repr_variant:ident),* ] , [ $($repr_variant_aligned:ident),* ]) => {
110        #[derive(Copy, Clone, Debug, Eq, PartialEq)]
111        pub enum $repr_name {
112            $($repr_variant,)*
113            $($repr_variant_aligned(u64),)*
114        }
115
116        impl KindRepr for $repr_name {
117            fn is_align(&self) -> bool {
118                match self {
119                    $($repr_name::$repr_variant_aligned(_) => true,)*
120                    _ => false,
121                }
122            }
123
124            fn is_align_gt_one(&self) -> bool {
125                match self {
126                    // `packed(n)` only lowers alignment
127                    $repr_name::Align(n) => n > &1,
128                    _ => false,
129                }
130            }
131
132            fn parse(meta: &Meta) -> syn::Result<$repr_name> {
133                match Repr::from_meta(meta)? {
134                    $(Repr::$repr_variant => Ok($repr_name::$repr_variant),)*
135                    $(Repr::$repr_variant_aligned(u) => Ok($repr_name::$repr_variant_aligned(u)),)*
136                    _ => Err(Error::new_spanned(meta, concat!("unsupported representation for deriving FromBytes, AsBytes, or Unaligned on ", $type_name)))
137                }
138            }
139        }
140
141        // Define a stable ordering so we can canonicalize lists of reprs. The
142        // ordering itself doesn't matter so long as it's stable.
143        impl PartialOrd for $repr_name {
144            fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
145                Some(self.cmp(other))
146            }
147        }
148
149        impl Ord for $repr_name {
150            fn cmp(&self, other: &Self) -> core::cmp::Ordering {
151                format!("{:?}", self).cmp(&format!("{:?}", other))
152            }
153        }
154
155        impl core::fmt::Display for $repr_name {
156            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
157                match self {
158                    $($repr_name::$repr_variant => Repr::$repr_variant,)*
159                    $($repr_name::$repr_variant_aligned(u) => Repr::$repr_variant_aligned(*u),)*
160                }.fmt(f)
161            }
162        }
163    }
164}
165
166define_kind_specific_repr!("a struct", StructRepr, [C, Transparent, Packed], [Align, PackedN]);
167define_kind_specific_repr!(
168    "an enum",
169    EnumRepr,
170    [C, U8, U16, U32, U64, Usize, I8, I16, I32, I64, Isize],
171    [Align]
172);
173
174// All representations known to Rust.
175#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
176pub enum Repr {
177    U8,
178    U16,
179    U32,
180    U64,
181    Usize,
182    I8,
183    I16,
184    I32,
185    I64,
186    Isize,
187    C,
188    Transparent,
189    Packed,
190    PackedN(u64),
191    Align(u64),
192}
193
194impl Repr {
195    fn from_meta(meta: &Meta) -> Result<Repr, Error> {
196        let (path, list) = match meta {
197            Meta::Path(path) => (path, None),
198            Meta::List(list) => (&list.path, Some(list)),
199            _ => return Err(Error::new_spanned(meta, "unrecognized representation hint")),
200        };
201
202        let ident = path
203            .get_ident()
204            .ok_or_else(|| Error::new_spanned(meta, "unrecognized representation hint"))?;
205
206        Ok(match (ident.to_string().as_str(), list) {
207            ("u8", None) => Repr::U8,
208            ("u16", None) => Repr::U16,
209            ("u32", None) => Repr::U32,
210            ("u64", None) => Repr::U64,
211            ("usize", None) => Repr::Usize,
212            ("i8", None) => Repr::I8,
213            ("i16", None) => Repr::I16,
214            ("i32", None) => Repr::I32,
215            ("i64", None) => Repr::I64,
216            ("isize", None) => Repr::Isize,
217            ("C", None) => Repr::C,
218            ("transparent", None) => Repr::Transparent,
219            ("packed", None) => Repr::Packed,
220            ("packed", Some(list)) => {
221                Repr::PackedN(list.parse_args::<LitInt>()?.base10_parse::<u64>()?)
222            }
223            ("align", Some(list)) => {
224                Repr::Align(list.parse_args::<LitInt>()?.base10_parse::<u64>()?)
225            }
226            _ => return Err(Error::new_spanned(meta, "unrecognized representation hint")),
227        })
228    }
229}
230
231impl KindRepr for Repr {
232    fn is_align(&self) -> bool {
233        false
234    }
235
236    fn is_align_gt_one(&self) -> bool {
237        false
238    }
239
240    fn parse(meta: &Meta) -> syn::Result<Self> {
241        Self::from_meta(meta)
242    }
243}
244
245impl Display for Repr {
246    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
247        if let Repr::Align(n) = self {
248            return write!(f, "repr(align({}))", n);
249        }
250        if let Repr::PackedN(n) = self {
251            return write!(f, "repr(packed({}))", n);
252        }
253        write!(
254            f,
255            "repr({})",
256            match self {
257                Repr::U8 => "u8",
258                Repr::U16 => "u16",
259                Repr::U32 => "u32",
260                Repr::U64 => "u64",
261                Repr::Usize => "usize",
262                Repr::I8 => "i8",
263                Repr::I16 => "i16",
264                Repr::I32 => "i32",
265                Repr::I64 => "i64",
266                Repr::Isize => "isize",
267                Repr::C => "C",
268                Repr::Transparent => "transparent",
269                Repr::Packed => "packed",
270                _ => unreachable!(),
271            }
272        )
273    }
274}
275
276pub(crate) fn reprs<R: KindRepr>(attrs: &[Attribute]) -> Result<Vec<(Meta, R)>, Vec<Error>> {
277    let mut reprs = Vec::new();
278    let mut errors = Vec::new();
279    for attr in attrs {
280        // Ignore documentation attributes.
281        if attr.path().is_ident("doc") {
282            continue;
283        }
284        if let Meta::List(ref meta_list) = attr.meta {
285            if meta_list.path.is_ident("repr") {
286                let parsed: Punctuated<Meta, Comma> =
287                    match meta_list.parse_args_with(Punctuated::parse_terminated) {
288                        Ok(parsed) => parsed,
289                        Err(_) => {
290                            errors.push(Error::new_spanned(
291                                &meta_list.tokens,
292                                "unrecognized representation hint",
293                            ));
294                            continue;
295                        }
296                    };
297                for meta in parsed {
298                    match R::parse(&meta) {
299                        Ok(repr) => reprs.push((meta, repr)),
300                        Err(err) => errors.push(err),
301                    }
302                }
303            }
304        }
305    }
306
307    if !errors.is_empty() {
308        return Err(errors);
309    }
310    Ok(reprs)
311}