summaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs324
1 files changed, 279 insertions, 45 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 6b9e3a4..9af8a28 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,10 @@
-// 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.
+// 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.
//! Derive macros for [zerocopy]'s traits.
//!
@@ -38,6 +42,17 @@ use {
use {crate::ext::*, crate::repr::*};
+// Unwraps a `Result<_, Vec<Error>>`, converting any `Err` value into a
+// `TokenStream` and returning it.
+macro_rules! try_or_print {
+ ($e:expr) => {
+ match $e {
+ Ok(x) => x,
+ Err(errors) => return print_all_errors(errors).into(),
+ }
+ };
+}
+
// TODO(https://github.com/rust-lang/rust/issues/54140): Some errors could be
// made better if we could add multiple lines of error output like this:
//
@@ -55,6 +70,179 @@ use {crate::ext::*, crate::repr::*};
// (https://doc.rust-lang.org/nightly/proc_macro/struct.Span.html#method.error),
// which is currently unstable. Revisit this once it's stable.
+#[proc_macro_derive(KnownLayout)]
+pub fn derive_known_layout(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let ast = syn::parse_macro_input!(ts as DeriveInput);
+
+ let is_repr_c_struct = match &ast.data {
+ Data::Struct(..) => {
+ let reprs = try_or_print!(repr::reprs::<Repr>(&ast.attrs));
+ if reprs.iter().any(|(_meta, repr)| repr == &Repr::C) {
+ Some(reprs)
+ } else {
+ None
+ }
+ }
+ Data::Enum(..) | Data::Union(..) => None,
+ };
+
+ let fields = ast.data.field_types();
+
+ let (require_self_sized, extras) = if let (
+ Some(reprs),
+ Some((trailing_field, leading_fields)),
+ ) = (is_repr_c_struct, fields.split_last())
+ {
+ let repr_align = reprs
+ .iter()
+ .find_map(
+ |(_meta, repr)| {
+ if let Repr::Align(repr_align) = repr {
+ Some(repr_align)
+ } else {
+ None
+ }
+ },
+ )
+ .map(|repr_align| quote!(NonZeroUsize::new(#repr_align as usize)))
+ .unwrap_or(quote!(None));
+
+ let repr_packed = reprs
+ .iter()
+ .find_map(|(_meta, repr)| match repr {
+ Repr::Packed => Some(1),
+ Repr::PackedN(repr_packed) => Some(*repr_packed),
+ _ => None,
+ })
+ .map(|repr_packed| quote!(NonZeroUsize::new(#repr_packed as usize)))
+ .unwrap_or(quote!(None));
+
+ (
+ false,
+ quote!(
+ // SAFETY: `LAYOUT` accurately describes the layout of `Self`.
+ // The layout of `Self` is reflected using a sequence of
+ // invocations of `DstLayout::{new_zst,extend,pad_to_align}`.
+ // The documentation of these items vows that invocations in
+ // this manner will acurately describe a type, so long as:
+ //
+ // - that type is `repr(C)`,
+ // - its fields are enumerated in the order they appear,
+ // - the presence of `repr_align` and `repr_packed` are correctly accounted for.
+ //
+ // We respect all three of these preconditions here. This
+ // expansion is only used if `is_repr_c_struct`, we enumerate
+ // the fields in order, and we extract the values of `align(N)`
+ // and `packed(N)`.
+ const LAYOUT: ::zerocopy::DstLayout = {
+ use ::zerocopy::macro_util::core_reexport::num::NonZeroUsize;
+ use ::zerocopy::{DstLayout, KnownLayout};
+
+ let repr_align = #repr_align;
+ let repr_packed = #repr_packed;
+
+ DstLayout::new_zst(repr_align)
+ #(.extend(DstLayout::for_type::<#leading_fields>(), repr_packed))*
+ .extend(<#trailing_field as KnownLayout>::LAYOUT, repr_packed)
+ .pad_to_align()
+ };
+
+ // SAFETY:
+ // - The recursive call to `raw_from_ptr_len` preserves both address and provenance.
+ // - The `as` cast preserves both address and provenance.
+ // - `NonNull::new_unchecked` preserves both address and provenance.
+ #[inline(always)]
+ fn raw_from_ptr_len(
+ bytes: ::zerocopy::macro_util::core_reexport::ptr::NonNull<u8>,
+ elems: usize,
+ ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> {
+ use ::zerocopy::{KnownLayout};
+ let trailing = <#trailing_field as KnownLayout>::raw_from_ptr_len(bytes, elems);
+ let slf = trailing.as_ptr() as *mut Self;
+ // SAFETY: Constructed from `trailing`, which is non-null.
+ unsafe { ::zerocopy::macro_util::core_reexport::ptr::NonNull::new_unchecked(slf) }
+ }
+ ),
+ )
+ } else {
+ // For enums, unions, and non-`repr(C)` structs, we require that
+ // `Self` is sized, and as a result don't need to reason about the
+ // internals of the type.
+ (
+ true,
+ quote!(
+ // SAFETY: `LAYOUT` is guaranteed to accurately describe the
+ // layout of `Self`, because that is the documented safety
+ // contract of `DstLayout::for_type`.
+ const LAYOUT: ::zerocopy::DstLayout = ::zerocopy::DstLayout::for_type::<Self>();
+
+ // SAFETY: `.cast` preserves address and provenance.
+ //
+ // TODO(#429): Add documentation to `.cast` that promises that
+ // it preserves provenance.
+ #[inline(always)]
+ fn raw_from_ptr_len(
+ bytes: ::zerocopy::macro_util::core_reexport::ptr::NonNull<u8>,
+ _elems: usize,
+ ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> {
+ bytes.cast::<Self>()
+ }
+ ),
+ )
+ };
+
+ match &ast.data {
+ Data::Struct(strct) => {
+ let require_trait_bound_on_field_types = if require_self_sized {
+ RequireBoundedFields::No
+ } else {
+ RequireBoundedFields::Trailing
+ };
+
+ // A bound on the trailing field is required, since structs are
+ // unsized if their trailing field is unsized. Reflecting the layout
+ // of an usized trailing field requires that the field is
+ // `KnownLayout`.
+ impl_block(
+ &ast,
+ strct,
+ Trait::KnownLayout,
+ require_trait_bound_on_field_types,
+ require_self_sized,
+ None,
+ Some(extras),
+ )
+ }
+ Data::Enum(enm) => {
+ // A bound on the trailing field is not required, since enums cannot
+ // currently be unsized.
+ impl_block(
+ &ast,
+ enm,
+ Trait::KnownLayout,
+ RequireBoundedFields::No,
+ true,
+ None,
+ Some(extras),
+ )
+ }
+ Data::Union(unn) => {
+ // A bound on the trailing field is not required, since unions
+ // cannot currently be unsized.
+ impl_block(
+ &ast,
+ unn,
+ Trait::KnownLayout,
+ RequireBoundedFields::No,
+ true,
+ None,
+ Some(extras),
+ )
+ }
+ }
+ .into()
+}
+
#[proc_macro_derive(FromZeroes)]
pub fn derive_from_zeroes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(ts as DeriveInput);
@@ -99,17 +287,6 @@ pub fn derive_unaligned(ts: proc_macro::TokenStream) -> proc_macro::TokenStream
.into()
}
-// Unwraps a `Result<_, Vec<Error>>`, converting any `Err` value into a
-// `TokenStream` and returning it.
-macro_rules! try_or_print {
- ($e:expr) => {
- match $e {
- Ok(x) => x,
- Err(errors) => return print_all_errors(errors),
- }
- };
-}
-
const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr]] = &[
&[StructRepr::C],
&[StructRepr::Transparent],
@@ -121,7 +298,7 @@ const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr]] = &[
// - all fields are `FromZeroes`
fn derive_from_zeroes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
- impl_block(ast, strct, "FromZeroes", true, None)
+ impl_block(ast, strct, Trait::FromZeroes, RequireBoundedFields::Yes, false, None, None)
}
// An enum is `FromZeroes` if:
@@ -155,21 +332,21 @@ fn derive_from_zeroes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::To
.to_compile_error();
}
- impl_block(ast, enm, "FromZeroes", true, None)
+ impl_block(ast, enm, Trait::FromZeroes, RequireBoundedFields::Yes, false, None, None)
}
// Like structs, unions are `FromZeroes` if
// - all fields are `FromZeroes`
fn derive_from_zeroes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
- impl_block(ast, unn, "FromZeroes", true, None)
+ impl_block(ast, unn, Trait::FromZeroes, RequireBoundedFields::Yes, false, None, None)
}
// A struct is `FromBytes` if:
// - all fields are `FromBytes`
fn derive_from_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
- impl_block(ast, strct, "FromBytes", true, None)
+ impl_block(ast, strct, Trait::FromBytes, RequireBoundedFields::Yes, false, None, None)
}
// An enum is `FromBytes` if:
@@ -212,7 +389,7 @@ fn derive_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Tok
.to_compile_error();
}
- impl_block(ast, enm, "FromBytes", true, None)
+ impl_block(ast, enm, Trait::FromBytes, RequireBoundedFields::Yes, false, None, None)
}
#[rustfmt::skip]
@@ -243,7 +420,7 @@ const ENUM_FROM_BYTES_CFG: Config<EnumRepr> = {
// - all fields are `FromBytes`
fn derive_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
- impl_block(ast, unn, "FromBytes", true, None)
+ impl_block(ast, unn, Trait::FromBytes, RequireBoundedFields::Yes, false, None, None)
}
// A struct is `AsBytes` if:
@@ -277,7 +454,7 @@ fn derive_as_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2:
// any padding bytes would need to come from the fields, all of which
// we require to be `AsBytes` (meaning they don't have any padding).
let padding_check = if is_transparent || is_packed { None } else { Some(PaddingCheck::Struct) };
- impl_block(ast, strct, "AsBytes", true, padding_check)
+ impl_block(ast, strct, Trait::AsBytes, RequireBoundedFields::Yes, false, padding_check, None)
}
const STRUCT_UNION_AS_BYTES_CFG: Config<StructRepr> = Config {
@@ -300,7 +477,7 @@ fn derive_as_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Token
// We don't care what the repr is; we only care that it is one of the
// allowed ones.
let _: Vec<repr::EnumRepr> = try_or_print!(ENUM_AS_BYTES_CFG.validate_reprs(ast));
- impl_block(ast, enm, "AsBytes", false, None)
+ impl_block(ast, enm, Trait::AsBytes, RequireBoundedFields::No, false, None, None)
}
#[rustfmt::skip]
@@ -342,7 +519,15 @@ fn derive_as_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::Tok
try_or_print!(STRUCT_UNION_AS_BYTES_CFG.validate_reprs(ast));
- impl_block(ast, unn, "AsBytes", true, Some(PaddingCheck::Union))
+ impl_block(
+ ast,
+ unn,
+ Trait::AsBytes,
+ RequireBoundedFields::Yes,
+ false,
+ Some(PaddingCheck::Union),
+ None,
+ )
}
// A struct is `Unaligned` if:
@@ -353,9 +538,9 @@ fn derive_as_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::Tok
fn derive_unaligned_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast));
- let require_trait_bound = !reprs.contains(&StructRepr::Packed);
+ let require_trait_bounds_on_field_types = (!reprs.contains(&StructRepr::Packed)).into();
- impl_block(ast, strct, "Unaligned", require_trait_bound, None)
+ impl_block(ast, strct, Trait::Unaligned, require_trait_bounds_on_field_types, false, None, None)
}
const STRUCT_UNION_UNALIGNED_CFG: Config<StructRepr> = Config {
@@ -383,10 +568,10 @@ fn derive_unaligned_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Toke
let _: Vec<repr::EnumRepr> = try_or_print!(ENUM_UNALIGNED_CFG.validate_reprs(ast));
// C-like enums cannot currently have type parameters, so this value of true
- // for `require_trait_bounds` doesn't really do anything. But it's
- // marginally more future-proof in case that restriction is lifted in the
- // future.
- impl_block(ast, enm, "Unaligned", true, None)
+ // for `require_trait_bound_on_field_types` doesn't really do anything. But
+ // it's marginally more future-proof in case that restriction is lifted in
+ // the future.
+ impl_block(ast, enm, Trait::Unaligned, RequireBoundedFields::Yes, false, None, None)
}
#[rustfmt::skip]
@@ -422,9 +607,9 @@ const ENUM_UNALIGNED_CFG: Config<EnumRepr> = {
fn derive_unaligned_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast));
- let require_trait_bound = !reprs.contains(&StructRepr::Packed);
+ let require_trait_bound_on_field_types = (!reprs.contains(&StructRepr::Packed)).into();
- impl_block(ast, unn, "Unaligned", require_trait_bound, None)
+ impl_block(ast, unn, Trait::Unaligned, require_trait_bound_on_field_types, false, None, None)
}
// This enum describes what kind of padding check needs to be generated for the
@@ -449,12 +634,45 @@ impl PaddingCheck {
}
}
+#[derive(Debug, Eq, PartialEq)]
+enum Trait {
+ KnownLayout,
+ FromZeroes,
+ FromBytes,
+ AsBytes,
+ Unaligned,
+}
+
+impl Trait {
+ fn ident(&self) -> Ident {
+ Ident::new(format!("{:?}", self).as_str(), Span::call_site())
+ }
+}
+
+#[derive(Debug, Eq, PartialEq)]
+enum RequireBoundedFields {
+ No,
+ Yes,
+ Trailing,
+}
+
+impl From<bool> for RequireBoundedFields {
+ fn from(do_require: bool) -> Self {
+ match do_require {
+ true => Self::Yes,
+ false => Self::No,
+ }
+ }
+}
+
fn impl_block<D: DataExt>(
input: &DeriveInput,
data: &D,
- trait_name: &str,
- require_trait_bound: bool,
+ trt: Trait,
+ require_trait_bound_on_field_types: RequireBoundedFields,
+ require_self_sized: bool,
padding_check: Option<PaddingCheck>,
+ extras: Option<proc_macro2::TokenStream>,
) -> proc_macro2::TokenStream {
// In this documentation, we will refer to this hypothetical struct:
//
@@ -515,14 +733,15 @@ fn impl_block<D: DataExt>(
// = note: required by `zerocopy::Unaligned`
let type_ident = &input.ident;
- let trait_ident = Ident::new(trait_name, Span::call_site());
+ let trait_ident = trt.ident();
let field_types = data.field_types();
- let field_type_bounds = require_trait_bound
- .then(|| field_types.iter().map(|ty| parse_quote!(#ty: zerocopy::#trait_ident)))
- .into_iter()
- .flatten()
- .collect::<Vec<_>>();
+ let bound_tt = |ty| parse_quote!(#ty: ::zerocopy::#trait_ident);
+ let field_type_bounds: Vec<_> = match (require_trait_bound_on_field_types, &field_types[..]) {
+ (RequireBoundedFields::Yes, _) => field_types.iter().map(bound_tt).collect(),
+ (RequireBoundedFields::No, _) | (RequireBoundedFields::Trailing, []) => vec![],
+ (RequireBoundedFields::Trailing, [.., last]) => vec![bound_tt(last)],
+ };
// Don't bother emitting a padding check if there are no fields.
#[allow(unstable_name_collisions)] // See `BoolExt` below
@@ -530,11 +749,13 @@ fn impl_block<D: DataExt>(
let fields = field_types.iter();
let validator_macro = check.validator_macro_ident();
parse_quote!(
- zerocopy::derive_util::HasPadding<#type_ident, {zerocopy::#validator_macro!(#type_ident, #(#fields),*)}>:
- zerocopy::derive_util::ShouldBe<false>
+ ::zerocopy::macro_util::HasPadding<#type_ident, {::zerocopy::#validator_macro!(#type_ident, #(#fields),*)}>:
+ ::zerocopy::macro_util::ShouldBe<false>
)
});
+ let self_sized_bound = if require_self_sized { Some(parse_quote!(Self: Sized)) } else { None };
+
let bounds = input
.generics
.where_clause
@@ -543,7 +764,8 @@ fn impl_block<D: DataExt>(
.into_iter()
.flatten()
.chain(field_type_bounds.iter())
- .chain(padding_check_bound.iter());
+ .chain(padding_check_bound.iter())
+ .chain(self_sized_bound.iter());
// The parameters with trait bounds, but without type defaults.
let params = input.generics.params.clone().into_iter().map(|mut param| {
@@ -554,22 +776,34 @@ fn impl_block<D: DataExt>(
}
quote!(#param)
});
+
// The identifiers of the parameters without trait bounds or type defaults.
let param_idents = input.generics.params.iter().map(|param| match param {
GenericParam::Type(ty) => {
let ident = &ty.ident;
quote!(#ident)
}
- GenericParam::Lifetime(l) => quote!(#l),
- GenericParam::Const(cnst) => quote!(#cnst),
+ GenericParam::Lifetime(l) => {
+ let ident = &l.lifetime;
+ quote!(#ident)
+ }
+ GenericParam::Const(cnst) => {
+ let ident = &cnst.ident;
+ quote!({#ident})
+ }
});
quote! {
- unsafe impl < #(#params),* > zerocopy::#trait_ident for #type_ident < #(#param_idents),* >
+ // TODO(#553): Add a test that generates a warning when
+ // `#[allow(deprecated)]` isn't present.
+ #[allow(deprecated)]
+ unsafe impl < #(#params),* > ::zerocopy::#trait_ident for #type_ident < #(#param_idents),* >
where
#(#bounds,)*
{
fn only_derive_is_allowed_to_implement_this_trait() {}
+
+ #extras
}
}
}