| 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 | |