diff options
Diffstat (limited to 'third_party/rust/zerocopy-derive/src/repr.rs')
-rw-r--r-- | third_party/rust/zerocopy-derive/src/repr.rs | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/third_party/rust/zerocopy-derive/src/repr.rs b/third_party/rust/zerocopy-derive/src/repr.rs new file mode 100644 index 0000000000..f4f2788685 --- /dev/null +++ b/third_party/rust/zerocopy-derive/src/repr.rs @@ -0,0 +1,311 @@ +// Copyright 2019 The Fuchsia Authors +// +// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0 +// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option. +// This file may not be copied, modified, or distributed except according to +// those terms. + +use core::fmt::{self, Display, Formatter}; + +use { + proc_macro2::Span, + syn::punctuated::Punctuated, + syn::spanned::Spanned, + syn::token::Comma, + syn::{Attribute, DeriveInput, Error, LitInt, Meta}, +}; + +pub struct Config<Repr: KindRepr> { + // A human-readable message describing what combinations of representations + // are allowed. This will be printed to the user if they use an invalid + // combination. + pub allowed_combinations_message: &'static str, + // Whether we're checking as part of `derive(Unaligned)`. If not, we can + // ignore `repr(align)`, which makes the code (and the list of valid repr + // combinations we have to enumerate) somewhat simpler. If we're checking + // for `Unaligned`, then in addition to checking against illegal + // combinations, we also check to see if there exists a `repr(align(N > 1))` + // attribute. + pub derive_unaligned: bool, + // Combinations which are valid for the trait. + pub allowed_combinations: &'static [&'static [Repr]], + // Combinations which are not valid for the trait, but are legal according + // to Rust. Any combination not in this or `allowed_combinations` is either + // illegal according to Rust or the behavior is unspecified. If the behavior + // is unspecified, it might become specified in the future, and that + // specification might not play nicely with our requirements. Thus, we + // reject combinations with unspecified behavior in addition to illegal + // combinations. + pub disallowed_but_legal_combinations: &'static [&'static [Repr]], +} + +impl<R: KindRepr> Config<R> { + /// Validate that `input`'s representation attributes conform to the + /// requirements specified by this `Config`. + /// + /// `validate_reprs` extracts the `repr` attributes, validates that they + /// conform to the requirements of `self`, and returns them. Regardless of + /// whether `align` attributes are considered during validation, they are + /// stripped out of the returned value since no callers care about them. + pub fn validate_reprs(&self, input: &DeriveInput) -> Result<Vec<R>, Vec<Error>> { + let mut metas_reprs = reprs(&input.attrs)?; + metas_reprs.sort_by(|a: &(_, R), b| a.1.partial_cmp(&b.1).unwrap()); + + if self.derive_unaligned { + if let Some((meta, _)) = + metas_reprs.iter().find(|&repr: &&(_, R)| repr.1.is_align_gt_one()) + { + return Err(vec![Error::new_spanned( + meta, + "cannot derive Unaligned with repr(align(N > 1))", + )]); + } + } + + let mut metas = Vec::new(); + let mut reprs = Vec::new(); + metas_reprs.into_iter().filter(|(_, repr)| !repr.is_align()).for_each(|(meta, repr)| { + metas.push(meta); + reprs.push(repr) + }); + + if reprs.is_empty() { + // Use `Span::call_site` to report this error on the + // `#[derive(...)]` itself. + return Err(vec![Error::new(Span::call_site(), "must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout")]); + } + + let initial_sp = metas[0].span(); + let err_span = metas.iter().skip(1).try_fold(initial_sp, |sp, meta| sp.join(meta.span())); + + if self.allowed_combinations.contains(&reprs.as_slice()) { + Ok(reprs) + } else if self.disallowed_but_legal_combinations.contains(&reprs.as_slice()) { + Err(vec![Error::new( + err_span.unwrap_or_else(|| input.span()), + self.allowed_combinations_message, + )]) + } else { + Err(vec![Error::new( + err_span.unwrap_or_else(|| input.span()), + "conflicting representation hints", + )]) + } + } +} + +// The type of valid reprs for a particular kind (enum, struct, union). +pub trait KindRepr: 'static + Sized + Ord { + fn is_align(&self) -> bool; + fn is_align_gt_one(&self) -> bool; + fn parse(meta: &Meta) -> syn::Result<Self>; +} + +// Defines an enum for reprs which are valid for a given kind (structs, enums, +// etc), and provide implementations of `KindRepr`, `Ord`, and `Display`, and +// those traits' super-traits. +macro_rules! define_kind_specific_repr { + ($type_name:expr, $repr_name:ident, [ $($repr_variant:ident),* ] , [ $($repr_variant_aligned:ident),* ]) => { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum $repr_name { + $($repr_variant,)* + $($repr_variant_aligned(u64),)* + } + + impl KindRepr for $repr_name { + fn is_align(&self) -> bool { + match self { + $($repr_name::$repr_variant_aligned(_) => true,)* + _ => false, + } + } + + fn is_align_gt_one(&self) -> bool { + match self { + // `packed(n)` only lowers alignment + $repr_name::Align(n) => n > &1, + _ => false, + } + } + + fn parse(meta: &Meta) -> syn::Result<$repr_name> { + match Repr::from_meta(meta)? { + $(Repr::$repr_variant => Ok($repr_name::$repr_variant),)* + $(Repr::$repr_variant_aligned(u) => Ok($repr_name::$repr_variant_aligned(u)),)* + _ => Err(Error::new_spanned(meta, concat!("unsupported representation for deriving FromBytes, AsBytes, or Unaligned on ", $type_name))) + } + } + } + + // Define a stable ordering so we can canonicalize lists of reprs. The + // ordering itself doesn't matter so long as it's stable. + impl PartialOrd for $repr_name { + fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { + Some(self.cmp(other)) + } + } + + impl Ord for $repr_name { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + format!("{:?}", self).cmp(&format!("{:?}", other)) + } + } + + impl core::fmt::Display for $repr_name { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + $($repr_name::$repr_variant => Repr::$repr_variant,)* + $($repr_name::$repr_variant_aligned(u) => Repr::$repr_variant_aligned(*u),)* + }.fmt(f) + } + } + } +} + +define_kind_specific_repr!("a struct", StructRepr, [C, Transparent, Packed], [Align, PackedN]); +define_kind_specific_repr!( + "an enum", + EnumRepr, + [C, U8, U16, U32, U64, Usize, I8, I16, I32, I64, Isize], + [Align] +); + +// All representations known to Rust. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Repr { + U8, + U16, + U32, + U64, + Usize, + I8, + I16, + I32, + I64, + Isize, + C, + Transparent, + Packed, + PackedN(u64), + Align(u64), +} + +impl Repr { + fn from_meta(meta: &Meta) -> Result<Repr, Error> { + let (path, list) = match meta { + Meta::Path(path) => (path, None), + Meta::List(list) => (&list.path, Some(list)), + _ => return Err(Error::new_spanned(meta, "unrecognized representation hint")), + }; + + let ident = path + .get_ident() + .ok_or_else(|| Error::new_spanned(meta, "unrecognized representation hint"))?; + + Ok(match (ident.to_string().as_str(), list) { + ("u8", None) => Repr::U8, + ("u16", None) => Repr::U16, + ("u32", None) => Repr::U32, + ("u64", None) => Repr::U64, + ("usize", None) => Repr::Usize, + ("i8", None) => Repr::I8, + ("i16", None) => Repr::I16, + ("i32", None) => Repr::I32, + ("i64", None) => Repr::I64, + ("isize", None) => Repr::Isize, + ("C", None) => Repr::C, + ("transparent", None) => Repr::Transparent, + ("packed", None) => Repr::Packed, + ("packed", Some(list)) => { + Repr::PackedN(list.parse_args::<LitInt>()?.base10_parse::<u64>()?) + } + ("align", Some(list)) => { + Repr::Align(list.parse_args::<LitInt>()?.base10_parse::<u64>()?) + } + _ => return Err(Error::new_spanned(meta, "unrecognized representation hint")), + }) + } +} + +impl KindRepr for Repr { + fn is_align(&self) -> bool { + false + } + + fn is_align_gt_one(&self) -> bool { + false + } + + fn parse(meta: &Meta) -> syn::Result<Self> { + Self::from_meta(meta) + } +} + +impl Display for Repr { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + if let Repr::Align(n) = self { + return write!(f, "repr(align({}))", n); + } + if let Repr::PackedN(n) = self { + return write!(f, "repr(packed({}))", n); + } + write!( + f, + "repr({})", + match self { + Repr::U8 => "u8", + Repr::U16 => "u16", + Repr::U32 => "u32", + Repr::U64 => "u64", + Repr::Usize => "usize", + Repr::I8 => "i8", + Repr::I16 => "i16", + Repr::I32 => "i32", + Repr::I64 => "i64", + Repr::Isize => "isize", + Repr::C => "C", + Repr::Transparent => "transparent", + Repr::Packed => "packed", + _ => unreachable!(), + } + ) + } +} + +pub(crate) fn reprs<R: KindRepr>(attrs: &[Attribute]) -> Result<Vec<(Meta, R)>, Vec<Error>> { + let mut reprs = Vec::new(); + let mut errors = Vec::new(); + for attr in attrs { + // Ignore documentation attributes. + if attr.path().is_ident("doc") { + continue; + } + if let Meta::List(ref meta_list) = attr.meta { + if meta_list.path.is_ident("repr") { + let parsed: Punctuated<Meta, Comma> = + match meta_list.parse_args_with(Punctuated::parse_terminated) { + Ok(parsed) => parsed, + Err(_) => { + errors.push(Error::new_spanned( + &meta_list.tokens, + "unrecognized representation hint", + )); + continue; + } + }; + for meta in parsed { + match R::parse(&meta) { + Ok(repr) => reprs.push((meta, repr)), + Err(err) => errors.push(err), + } + } + } + } + } + + if !errors.is_empty() { + return Err(errors); + } + Ok(reprs) +} |