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. |
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 { |
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. |
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 { |
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 { |
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 | |
245 | impl 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 | |
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 | |