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 
9 use core::fmt::{self, Display, Formatter};
10 
11 use {
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 
19 pub 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 
43 impl<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.
validate_reprs(&self, input: &DeriveInput) -> Result<Vec<R>, Vec<Error>>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).
99 pub trait KindRepr: 'static + Sized + Ord {
is_align(&self) -> bool100     fn is_align(&self) -> bool;
is_align_gt_one(&self) -> bool101     fn is_align_gt_one(&self) -> bool;
parse(meta: &Meta) -> syn::Result<Self>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.
108 macro_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 
166 define_kind_specific_repr!("a struct", StructRepr, [C, Transparent, Packed], [Align, PackedN]);
167 define_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)]
176 pub 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 
194 impl Repr {
from_meta(meta: &Meta) -> Result<Repr, Error>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 
231 impl KindRepr for Repr {
is_align(&self) -> bool232     fn is_align(&self) -> bool {
233         false
234     }
235 
is_align_gt_one(&self) -> bool236     fn is_align_gt_one(&self) -> bool {
237         false
238     }
239 
parse(meta: &Meta) -> syn::Result<Self>240     fn parse(meta: &Meta) -> syn::Result<Self> {
241         Self::from_meta(meta)
242     }
243 }
244 
245 impl Display for Repr {
fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error>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 
reprs<R: KindRepr>(attrs: &[Attribute]) -> Result<Vec<(Meta, R)>, Vec<Error>>276 pub(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 }
312