311 lines
11 KiB
Rust
311 lines
11 KiB
Rust
// 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)
|
|
}
|