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