863 lines · 30.1 KB · Rust
RawDownload
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT//// 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 std::num::NonZeroU32; use proc_macro2::{Span, TokenStream};use quote::{quote, quote_spanned, ToTokens};use syn::{    parse_quote, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error,    Expr, ExprLit, Field, GenericParam, Ident, Index, Lit, LitStr, Meta, Path, Type, Variant,    Visibility, WherePredicate,}; use crate::repr::{CompoundRepr, EnumRepr, PrimitiveRepr, Repr, Spanned}; pub(crate) struct Ctx {    pub(crate) ast: DeriveInput,    pub(crate) zerocopy_crate: Path,     // The value of the last `#[zerocopy(on_error = ...)]` attribute, or `false`    // if none is provided.    pub(crate) skip_on_error: bool,     // The span of the last `#[zerocopy(on_error = ...)]` attribute, if any.    pub(crate) on_error_span: Option<proc_macro2::Span>,} impl Ctx {    /// Attempt to extract a crate path from the provided attributes. Defaults to    /// `::zerocopy` if not found.    pub(crate) fn try_from_derive_input(ast: DeriveInput) -> Result<Self, Error> {        let mut path = parse_quote!(::zerocopy);        let mut skip_on_error = false;        let mut on_error_span = None;         for attr in &ast.attrs {            if let Meta::List(ref meta_list) = attr.meta {                if meta_list.path.is_ident("zerocopy") {                    attr.parse_nested_meta(|meta| {                        if meta.path.is_ident("crate") {                            let expr = meta.value().and_then(|value| value.parse());                            if let Ok(Expr::Lit(ExprLit { lit: Lit::Str(lit), .. })) = expr {                                if let Ok(path_lit) = lit.parse::<Ident>() {                                    path = parse_quote!(::#path_lit);                                    return Ok(());                                }                            }                             return Err(Error::new(                                Span::call_site(),                                "`crate` attribute requires a path as the value",                            ));                        }                         if meta.path.is_ident("on_error") {                            on_error_span = Some(meta.path.span());                            let value = meta.value()?;                            let s: LitStr = value.parse()?;                            match s.value().as_str() {                                "skip" => skip_on_error = true,                                "fail" => skip_on_error = false,                                _ => return Err(Error::new(                                    s.span(),                                    "unrecognized value for `on_error` attribute from `zerocopy`; expected `skip` or `fail`",                                )),                            }                            return Ok(());                        }                         Err(Error::new(                            Span::call_site(),                            format!(                                "unknown attribute encountered: {}",                                meta.path.into_token_stream()                            ),                        ))                    })?;                }            }        }         Ok(Self { ast, zerocopy_crate: path, skip_on_error, on_error_span })    }     pub(crate) fn with_input(&self, input: &DeriveInput) -> Self {        Self {            ast: input.clone(),            zerocopy_crate: self.zerocopy_crate.clone(),            skip_on_error: self.skip_on_error,            on_error_span: self.on_error_span,        }    }     pub(crate) fn skip_on_error(mut self) -> Self {        self.skip_on_error = true;        self    }     pub(crate) fn core_path(&self) -> TokenStream {        let zerocopy_crate = &self.zerocopy_crate;        quote!(#zerocopy_crate::util::macro_util::core_reexport)    }     pub(crate) fn cfg_compile_error(&self) -> TokenStream {        // By checking both during the compilation of the proc macro *and* in        // the generated code, we ensure that `--cfg        // zerocopy_unstable_linux` need only be passed *either* when        // compiling this crate *or* when compiling the user's crate. The former        // is preferable, but in some situations (such as when cross-compiling        // using `cargo build --target`), it doesn't get propagated to this        // crate's build by default.        if cfg!(zerocopy_unstable_linux) {            quote!()        } else if let Some(span) = self.on_error_span {            let core = self.core_path();            let error_message =                "`on_error` is experimental; pass '--cfg zerocopy_unstable_linux' to enable";            quote::quote_spanned! {span=>                #[allow(unused_attributes, unexpected_cfgs)]                const _: () = {                    #[cfg(not(zerocopy_unstable_linux))]                    #core::compile_error!(#error_message);                };            }        } else {            quote!()        }    }     pub(crate) fn error_or_skip<E>(&self, error: E) -> Result<TokenStream, E> {        if self.skip_on_error {            Ok(self.cfg_compile_error())        } else {            Err(error)        }    }} pub(crate) trait DataExt {    /// Extracts the names and types of all fields. For enums, extracts the    /// names and types of fields from each variant. For tuple structs, the    /// names are the indices used to index into the struct (ie, `0`, `1`, etc).    ///    /// FIXME: Extracting field names for enums doesn't really make sense. Types    /// makes sense because we don't care about where they live - we just care    /// about transitive ownership. But for field names, we'd only use them when    /// generating is_bit_valid, which cares about where they live.    fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)>;     fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)>;     fn tag(&self) -> Option<Ident>;} impl DataExt for Data {    fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {        match self {            Data::Struct(strc) => strc.fields(),            Data::Enum(enm) => enm.fields(),            Data::Union(un) => un.fields(),        }    }     fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {        match self {            Data::Struct(strc) => strc.variants(),            Data::Enum(enm) => enm.variants(),            Data::Union(un) => un.variants(),        }    }     fn tag(&self) -> Option<Ident> {        match self {            Data::Struct(strc) => strc.tag(),            Data::Enum(enm) => enm.tag(),            Data::Union(un) => un.tag(),        }    }} impl DataExt for DataStruct {    fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {        map_fields(&self.fields)    }     fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {        vec![(None, self.fields())]    }     fn tag(&self) -> Option<Ident> {        None    }} impl DataExt for DataEnum {    fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {        map_fields(self.variants.iter().flat_map(|var| &var.fields))    }     fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {        self.variants.iter().map(|var| (Some(var), map_fields(&var.fields))).collect()    }     fn tag(&self) -> Option<Ident> {        Some(Ident::new("___ZerocopyTag", Span::call_site()))    }} impl DataExt for DataUnion {    fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {        map_fields(&self.fields.named)    }     fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {        vec![(None, self.fields())]    }     fn tag(&self) -> Option<Ident> {        None    }} fn map_fields<'a>(    fields: impl 'a + IntoIterator<Item = &'a Field>,) -> Vec<(&'a Visibility, TokenStream, &'a Type)> {    fields        .into_iter()        .enumerate()        .map(|(idx, f)| {            (                &f.vis,                f.ident                    .as_ref()                    .map(ToTokens::to_token_stream)                    .unwrap_or_else(|| Index::from(idx).to_token_stream()),                &f.ty,            )        })        .collect()} pub(crate) fn to_ident_str(t: &impl ToString) -> String {    let s = t.to_string();    if let Some(stripped) = s.strip_prefix("r#") {        stripped.to_string()    } else {        s    }} /// This enum describes what kind of padding check needs to be generated for the/// associated impl.pub(crate) enum PaddingCheck {    /// Check that the sum of the fields' sizes exactly equals the struct's    /// size.    Struct,    /// Check that a `repr(C)` struct has no padding.    ReprCStruct,    /// Check that the size of each field exactly equals the union's size.    Union,    /// Check that every variant of the enum contains no padding.    ///    /// Because doing so requires a tag enum, this padding check requires an    /// additional `TokenStream` which defines the tag enum as `___ZerocopyTag`.    Enum { tag_type_definition: TokenStream },} impl PaddingCheck {    /// Returns the idents of the trait to use and the macro to call in order to    /// validate that a type passes the relevant padding check.    pub(crate) fn validator_trait_and_macro_idents(&self) -> (Ident, Ident) {        let (trt, mcro) = match self {            PaddingCheck::Struct => ("PaddingFree", "struct_padding"),            PaddingCheck::ReprCStruct => ("DynamicPaddingFree", "repr_c_struct_has_padding"),            PaddingCheck::Union => ("PaddingFree", "union_padding"),            PaddingCheck::Enum { .. } => ("PaddingFree", "enum_padding"),        };         let trt = Ident::new(trt, Span::call_site());        let mcro = Ident::new(mcro, Span::call_site());        (trt, mcro)    }     /// Sometimes performing the padding check requires some additional    /// "context" code. For enums, this is the definition of the tag enum.    pub(crate) fn validator_macro_context(&self) -> Option<&TokenStream> {        match self {            PaddingCheck::Struct | PaddingCheck::ReprCStruct | PaddingCheck::Union => None,            PaddingCheck::Enum { tag_type_definition } => Some(tag_type_definition),        }    }} #[derive(Clone)]pub(crate) enum Trait {    KnownLayout,    HasTag,    HasField {        variant_id: Box<Expr>,        field: Box<Type>,        field_id: Box<Expr>,    },    ProjectField {        variant_id: Box<Expr>,        field: Box<Type>,        field_id: Box<Expr>,        invariants: Box<Type>,    },    Immutable,    TryFromBytes,    FromZeros,    FromBytes,    IntoBytes,    Unaligned,    Sized,    ByteHash,    ByteEq,    SplitAt,} impl ToTokens for Trait {    fn to_tokens(&self, tokens: &mut TokenStream) {        // According to [1], the format of the derived `Debug`` output is not        // stable and therefore not guaranteed to represent the variant names.        // Indeed with the (unstable) `fmt-debug` compiler flag [2], it can        // return only a minimalized output or empty string. To make sure this        // code will work in the future and independent of the compiler flag, we        // translate the variants to their names manually here.        //        // [1] https://doc.rust-lang.org/1.81.0/std/fmt/trait.Debug.html#stability        // [2] https://doc.rust-lang.org/beta/unstable-book/compiler-flags/fmt-debug.html        let s = match self {            Trait::HasField { .. } => "HasField",            Trait::ProjectField { .. } => "ProjectField",            Trait::KnownLayout => "KnownLayout",            Trait::HasTag => "HasTag",            Trait::Immutable => "Immutable",            Trait::TryFromBytes => "TryFromBytes",            Trait::FromZeros => "FromZeros",            Trait::FromBytes => "FromBytes",            Trait::IntoBytes => "IntoBytes",            Trait::Unaligned => "Unaligned",            Trait::Sized => "Sized",            Trait::ByteHash => "ByteHash",            Trait::ByteEq => "ByteEq",            Trait::SplitAt => "SplitAt",        };        let ident = Ident::new(s, Span::call_site());        let arguments: Option<syn::AngleBracketedGenericArguments> = match self {            Trait::HasField { variant_id, field, field_id } => {                Some(parse_quote!(<#field, #variant_id, #field_id>))            }            Trait::ProjectField { variant_id, field, field_id, invariants } => {                Some(parse_quote!(<#field, #invariants, #variant_id, #field_id>))            }            Trait::KnownLayout            | Trait::HasTag            | Trait::Immutable            | Trait::TryFromBytes            | Trait::FromZeros            | Trait::FromBytes            | Trait::IntoBytes            | Trait::Unaligned            | Trait::Sized            | Trait::ByteHash            | Trait::ByteEq            | Trait::SplitAt => None,        };        tokens.extend(quote!(#ident #arguments));    }} impl Trait {    pub(crate) fn crate_path(&self, ctx: &Ctx) -> Path {        let zerocopy_crate = &ctx.zerocopy_crate;        let core = ctx.core_path();        match self {            Self::Sized => parse_quote!(#core::marker::#self),            _ => parse_quote!(#zerocopy_crate::#self),        }    }} pub(crate) enum TraitBound {    Slf,    Other(Trait),} pub(crate) enum FieldBounds<'a> {    None,    All(&'a [TraitBound]),    Trailing(&'a [TraitBound]),    Explicit(Vec<WherePredicate>),} impl<'a> FieldBounds<'a> {    pub(crate) const ALL_SELF: FieldBounds<'a> = FieldBounds::All(&[TraitBound::Slf]);    pub(crate) const TRAILING_SELF: FieldBounds<'a> = FieldBounds::Trailing(&[TraitBound::Slf]);} pub(crate) enum SelfBounds<'a> {    None,    All(&'a [Trait]),} // FIXME(https://github.com/rust-lang/rust-clippy/issues/12908): This is a false// positive. Explicit lifetimes are actually necessary here.#[allow(clippy::needless_lifetimes)]impl<'a> SelfBounds<'a> {    pub(crate) const SIZED: Self = Self::All(&[Trait::Sized]);} /// Normalizes a slice of bounds by replacing [`TraitBound::Slf`] with `slf`.pub(crate) fn normalize_bounds<'a>(    slf: &'a Trait,    bounds: &'a [TraitBound],) -> impl 'a + Iterator<Item = Trait> {    bounds.iter().map(move |bound| match bound {        TraitBound::Slf => slf.clone(),        TraitBound::Other(trt) => trt.clone(),    })} pub(crate) struct ImplBlockBuilder<'a> {    ctx: &'a Ctx,    data: &'a dyn DataExt,    trt: Trait,    field_type_trait_bounds: FieldBounds<'a>,    self_type_trait_bounds: SelfBounds<'a>,    padding_check: Option<PaddingCheck>,    param_extras: Vec<GenericParam>,    inner_extras: Option<TokenStream>,    outer_extras: Option<TokenStream>,} impl<'a> ImplBlockBuilder<'a> {    pub(crate) fn new(        ctx: &'a Ctx,        data: &'a dyn DataExt,        trt: Trait,        field_type_trait_bounds: FieldBounds<'a>,    ) -> Self {        Self {            ctx,            data,            trt,            field_type_trait_bounds,            self_type_trait_bounds: SelfBounds::None,            padding_check: None,            param_extras: Vec::new(),            inner_extras: None,            outer_extras: None,        }    }     pub(crate) fn self_type_trait_bounds(mut self, self_type_trait_bounds: SelfBounds<'a>) -> Self {        self.self_type_trait_bounds = self_type_trait_bounds;        self    }     pub(crate) fn padding_check<P: Into<Option<PaddingCheck>>>(mut self, padding_check: P) -> Self {        self.padding_check = padding_check.into();        self    }     pub(crate) fn param_extras(mut self, param_extras: Vec<GenericParam>) -> Self {        self.param_extras.extend(param_extras);        self    }     pub(crate) fn inner_extras(mut self, inner_extras: TokenStream) -> Self {        self.inner_extras = Some(inner_extras);        self    }     pub(crate) fn outer_extras<T: Into<Option<TokenStream>>>(mut self, outer_extras: T) -> Self {        self.outer_extras = outer_extras.into();        self    }     pub(crate) fn build(self) -> TokenStream {        // In this documentation, we will refer to this hypothetical struct:        //        //   #[derive(FromBytes)]        //   struct Foo<T, I: Iterator>        //   where        //       T: Copy,        //       I: Clone,        //       I::Item: Clone,        //   {        //       a: u8,        //       b: T,        //       c: I::Item,

Lines 1–500 of 863.