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