Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,30 @@ const _: () = unsafe {
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T::MaybeUninit)] MaybeUninit<T>)
};

/// Projects a given field from `Self`.
///
/// All implementations of `HasField` for a particular field `f` in `Self`
/// should use the same `Field` type; this ensures that `Field` is inferable
/// given an explicit `VARIANT_ID` and `FIELD_ID`.
///
/// # Safety
///
/// A field `f` is `HasField` for `Self` if and only if:
///
/// - if `f` has name `n`, `FIELD_ID` is `zerocopy::ident_id!(n)`; otherwise, if
/// `f` is at index `i`, `FIELD_ID` is `zerocopy::ident_id!(i)`.
/// - `Field` is a type with the same visibility as `f`.
/// - `Type` has the same type as `f`.
#[doc(hidden)]
pub unsafe trait HasField<Field, const VARIANT_ID: u128, const FIELD_ID: u128> {
fn only_derive_is_allowed_to_implement_this_trait()
where
Self: Sized;

/// The type of the field.
type Type: ?Sized;
}

/// Analyzes whether a type is [`FromZeros`].
///
/// This derive analyzes, at compile time, whether the annotated type satisfies
Expand Down
60 changes: 59 additions & 1 deletion src/util/macro_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ use crate::{
FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout, Ptr, TryFromBytes, ValidityError,
};

/// Projects the type of the field at `Index` in `Self`.
/// Projects the type of the field at `Index` in `Self` without regard for field
/// privacy.
///
/// The `Index` parameter is any sort of handle that identifies the field; its
/// definition is the obligation of the implementer.
Expand Down Expand Up @@ -529,6 +530,63 @@ macro_rules! assert_size_eq {
}};
}

/// Translates an identifier or tuple index into a numeric identifier.
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
#[macro_export]
macro_rules! ident_id {
($field:ident) => {
$crate::util::macro_util::hash_name(stringify!($field))
};
($field:literal) => {
$field
};
}

/// Computes the hash of a string.
///
/// NOTE(#2749) on hash collisions: This function's output only needs to be
/// deterministic within a particular compilation. Thus, if a user ever reports
/// a hash collision (very unlikely given the <= 16-byte special case), we can
/// strengthen the hash function at that point and publish a new version. Since
/// this is computed at compile time on small strings, we can easily use more
/// expensive and higher-quality hash functions if need be.
#[inline(always)]
#[must_use]
#[allow(clippy::as_conversions, clippy::indexing_slicing, clippy::arithmetic_side_effects)]
pub const fn hash_name(name: &str) -> u128 {
let name = name.as_bytes();

// We guarantee freedom from hash collisions between any two strings of
// length 16 or less by having the hashes of such strings be equal to
// their value. There is still a possibility that such strings will have
// the same value as the hash of a string of length > 16.
if name.len() <= size_of::<u128>() {
let mut bytes = [0u8; 16];

let mut i = 0;
while i < name.len() {
bytes[i] = name[i];
i += 1;
}

return u128::from_ne_bytes(bytes);
};

// An implementation of FxHasher, although returning a u128. Probably
// not as strong as it could be, but probably more collision resistant
// than normal 64-bit FxHasher.
let mut hash = 0u128;
let mut i = 0;
while i < name.len() {
// This is just FxHasher's `0x517cc1b727220a95` constant
// concatenated back-to-back.
const K: u128 = 0x517cc1b727220a95517cc1b727220a95;
hash = (hash.rotate_left(5) ^ (name[i] as u128)).wrapping_mul(K);
i += 1;
}
hash
}

/// Is a given source a valid instance of `Dst`?
///
/// If so, returns `src` casted to a `Ptr<Dst, _>`. Otherwise returns `None`.
Expand Down
43 changes: 39 additions & 4 deletions zerocopy-derive/src/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
// This file may not be copied, modified, or distributed except according to
// those terms.

use proc_macro2::{Span, TokenStream};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_quote, DataEnum, Error, Fields, Generics, Ident, Path};
use syn::{
parse_quote, spanned::Spanned as _, DataEnum, DeriveInput, Error, Fields, Generics, Ident, Path,
};

use crate::{derive_try_from_bytes_inner, repr::EnumRepr, Trait};
use crate::{
derive_try_from_bytes_inner, repr::EnumRepr, DataExt, FieldBounds, ImplBlockBuilder, Trait,
};

/// Generates a tag enum for the given enum. This generates an enum with the
/// same non-align `repr`s, variants, and corresponding discriminants, but none
Expand Down Expand Up @@ -219,6 +223,7 @@ fn generate_variants_union(generics: &Generics, data: &DataEnum) -> TokenStream
/// - `repr(int)`: <https://doc.rust-lang.org/reference/type-layout.html#primitive-representation-of-enums-with-fields>
/// - `repr(C, int)`: <https://doc.rust-lang.org/reference/type-layout.html#combining-primitive-representations-of-enums-with-fields-and-reprc>
pub(crate) fn derive_is_bit_valid(
ast: &DeriveInput,
enum_ident: &Ident,
repr: &EnumRepr,
generics: &Generics,
Expand All @@ -235,7 +240,7 @@ pub(crate) fn derive_is_bit_valid(
(quote! { () }, quote! { ___ZerocopyTag })
} else {
return Err(Error::new(
Span::call_site(),
ast.span(),
"must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
));
};
Expand All @@ -245,6 +250,34 @@ pub(crate) fn derive_is_bit_valid(

let (_, ty_generics, _) = generics.split_for_impl();

let has_fields = data.variants().into_iter().flat_map(|(variant, fields)| {
let variant_ident = &variant.unwrap().ident;
fields.into_iter().map(move |(vis, ident, ty)| {
// Rust does not presently support explicit visibility modifiers on
// enum fields, but we guard against the possibility to ensure this
// derive remains sound.
assert!(matches!(vis, syn::Visibility::Inherited));
ImplBlockBuilder::new(
ast,
data,
Trait::HasField {
variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
// Since Rust does not presently support explicit visibility
// modifiers on enum fields, any public type is suitable
// here; we use `()`.
field: parse_quote!(()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to disregard, but it might be worth considering whether we could put this in a const or static or something to avoid recomputing it for every field.

field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
},
FieldBounds::None,
zerocopy_crate,
)
.inner_extras(quote! {
type Type = #ty;
})
.build()
})
});

let match_arms = data.variants.iter().map(|variant| {
let tag_ident = tag_ident(&variant.ident);
let variant_struct_ident = variant_struct_ident(&variant.ident);
Expand Down Expand Up @@ -323,6 +356,8 @@ pub(crate) fn derive_is_bit_valid(
variants: ___ZerocopyVariants #ty_generics,
}

#(#has_fields)*

let tag = {
// SAFETY:
// - The provided cast addresses a subset of the bytes addressed
Expand Down
18 changes: 9 additions & 9 deletions zerocopy-derive/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::{Data, DataEnum, DataStruct, DataUnion, Field, Ident, Index, Type, Visibility};
use syn::{Data, DataEnum, DataStruct, DataUnion, Field, Ident, Index, Type, Variant, Visibility};

pub(crate) trait DataExt {
/// Extracts the names and types of all fields. For enums, extracts the
Expand All @@ -21,7 +21,7 @@ pub(crate) trait DataExt {
/// generating is_bit_valid, which cares about where they live.
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)>;

fn variants(&self) -> Vec<Vec<(&Visibility, TokenStream, &Type)>>;
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)>;

fn tag(&self) -> Option<Ident>;
}
Expand All @@ -35,7 +35,7 @@ impl DataExt for Data {
}
}

fn variants(&self) -> Vec<Vec<(&Visibility, TokenStream, &Type)>> {
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
match self {
Data::Struct(strc) => strc.variants(),
Data::Enum(enm) => enm.variants(),
Expand All @@ -57,8 +57,8 @@ impl DataExt for DataStruct {
map_fields(&self.fields)
}

fn variants(&self) -> Vec<Vec<(&Visibility, TokenStream, &Type)>> {
vec![self.fields()]
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
vec![(None, self.fields())]
}

fn tag(&self) -> Option<Ident> {
Expand All @@ -71,8 +71,8 @@ impl DataExt for DataEnum {
map_fields(self.variants.iter().flat_map(|var| &var.fields))
}

fn variants(&self) -> Vec<Vec<(&Visibility, TokenStream, &Type)>> {
self.variants.iter().map(|var| map_fields(&var.fields)).collect()
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> {
Expand All @@ -85,8 +85,8 @@ impl DataExt for DataUnion {
map_fields(&self.fields.named)
}

fn variants(&self) -> Vec<Vec<(&Visibility, TokenStream, &Type)>> {
vec![self.fields()]
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
vec![(None, self.fields())]
}

fn tag(&self) -> Option<Ident> {
Expand Down
Loading