// Copyright 2019 The Fuchsia Authors // // Licensed under a BSD-style license , Apache License, Version 2.0 // , or the MIT // license , 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 { // 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 Config { /// 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> { 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; } // 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 { 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 { 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::()?.base10_parse::()?) } ("align", Some(list)) => { Repr::Align(list.parse_args::()?.base10_parse::()?) } _ => 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::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(attrs: &[Attribute]) -> Result, Vec> { 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 = 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) }