blob: 0868e3e4a3b91460ffe3196b6581e7eed1435430 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use core::fmt::{self, Display, Formatter};
use proc_macro2::Span;
use syn::spanned::Spanned;
use syn::{Attribute, DeriveInput, Error, Lit, Meta, NestedMeta};
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: &(NestedMeta, R), b| a.1.partial_cmp(&b.1).unwrap());
if self.derive_unaligned {
match metas_reprs.iter().find(|&repr: &&(NestedMeta, R)| repr.1.is_align_gt_one()) {
Some((meta, _)) => {
return Err(vec![Error::new_spanned(
meta,
"cannot derive Unaligned with repr(align(N > 1))",
)])
}
None => (),
}
}
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).fold(Some(initial_sp), |sp_option, meta| {
sp_option.and_then(|sp| 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(input.span()),
self.allowed_combinations_message,
)])
} else {
Err(vec![Error::new(
err_span.unwrap_or(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: &NestedMeta) -> syn::Result<Self>;
}
// Define 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),*) => {
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum $repr_name {
$($repr_variant,)*
Align(u64),
}
impl KindRepr for $repr_name {
fn is_align(&self) -> bool {
match self {
$repr_name::Align(_) => true,
_ => false,
}
}
fn is_align_gt_one(&self) -> bool {
match self {
$repr_name::Align(n) => n > &1,
_ => false,
}
}
fn parse(meta: &NestedMeta) -> syn::Result<$repr_name> {
match Repr::from_nested_meta(meta)? {
$(Repr::$repr_variant => Ok($repr_name::$repr_variant),)*
Repr::Align(u) => Ok($repr_name::Align(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::Align(u) => Repr::Align(*u),
}.fmt(f)
}
}
}
}
define_kind_specific_repr!("a struct", StructRepr, C, Transparent, Packed);
define_kind_specific_repr!(
"an enum", EnumRepr, C, U8, U16, U32, U64, Usize, I8, I16, I32, I64, Isize
);
// All representations known to Rust.
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Repr {
U8,
U16,
U32,
U64,
Usize,
I8,
I16,
I32,
I64,
Isize,
C,
Transparent,
Packed,
Align(u64),
}
impl Repr {
fn from_nested_meta(meta: &NestedMeta) -> Result<Repr, Error> {
match meta {
NestedMeta::Meta(Meta::Path(path)) => {
let ident = path
.get_ident()
.ok_or(Error::new_spanned(meta, "unrecognized representation hint"))?;
match format!("{}", ident).as_str() {
"u8" => return Ok(Repr::U8),
"u16" => return Ok(Repr::U16),
"u32" => return Ok(Repr::U32),
"u64" => return Ok(Repr::U64),
"usize" => return Ok(Repr::Usize),
"i8" => return Ok(Repr::I8),
"i16" => return Ok(Repr::I16),
"i32" => return Ok(Repr::I32),
"i64" => return Ok(Repr::I64),
"isize" => return Ok(Repr::Isize),
"C" => return Ok(Repr::C),
"transparent" => return Ok(Repr::Transparent),
"packed" => return Ok(Repr::Packed),
_ => {}
}
}
NestedMeta::Meta(Meta::List(list)) => {
if let [&NestedMeta::Lit(Lit::Int(ref n))] =
list.nested.iter().collect::<Vec<_>>().as_slice()
{
return Ok(Repr::Align(n.base10_parse::<u64>()?));
}
}
_ => {}
}
Err(Error::new_spanned(meta, "unrecognized representation hint"))
}
}
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);
}
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!(),
}
)
}
}
fn reprs<R: KindRepr>(attrs: &[Attribute]) -> Result<Vec<(NestedMeta, 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;
}
match attr.parse_meta() {
Ok(Meta::List(meta_list)) => {
if meta_list.path.is_ident("repr") {
for nested_meta in &meta_list.nested {
match R::parse(nested_meta) {
Ok(repr) => reprs.push((nested_meta.clone(), repr)),
Err(err) => errors.push(err),
}
}
}
}
Err(e) => errors.push(e),
_ => {}
}
}
if !errors.is_empty() {
return Err(errors);
}
Ok(reprs)
}