Skip to content

Commit e5dae4c

Browse files
committed
Introduce HasField trait
The `HasField` trait permits privacy-preserving field projections from structs, enums and unions. gherrit-pr-id: G2238bc341570838db412d880017b3f0c25ac09fa
1 parent 60f4102 commit e5dae4c

File tree

17 files changed

+2782
-829
lines changed

17 files changed

+2782
-829
lines changed

src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,6 +1087,30 @@ const _: () = unsafe {
10871087
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T::MaybeUninit)] MaybeUninit<T>)
10881088
};
10891089

1090+
/// Projects a given field from `Self`.
1091+
///
1092+
/// All implementations of `HasField` for a particular field `f` in `Self`
1093+
/// should use the same `Field` type; this ensures that `Field` is inferable
1094+
/// given an explicit `VARIANT_ID` and `FIELD_ID`.
1095+
///
1096+
/// # Safety
1097+
///
1098+
/// A field `f` is `HasField` for `Self` if and only if:
1099+
///
1100+
/// - if `f` has name `n`, `FIELD_ID` is `zerocopy::ident_id!(n)`; otherwise, if
1101+
/// `f` is at index `i`, `FIELD_ID` is `zerocopy::ident_id!(i)`.
1102+
/// - `Field` is a type with the same visibility as `f`.
1103+
/// - `Type` has the same type as `f`.
1104+
#[doc(hidden)]
1105+
pub unsafe trait HasField<Field, const VARIANT_ID: u128, const FIELD_ID: u128> {
1106+
fn only_derive_is_allowed_to_implement_this_trait()
1107+
where
1108+
Self: Sized;
1109+
1110+
/// The type of the field.
1111+
type Type: ?Sized;
1112+
}
1113+
10901114
/// Analyzes whether a type is [`FromZeros`].
10911115
///
10921116
/// This derive analyzes, at compile time, whether the annotated type satisfies

src/util/macro_util.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ use crate::{
3535
FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout, Ptr, TryFromBytes, ValidityError,
3636
};
3737

38-
/// Projects the type of the field at `Index` in `Self`.
38+
/// Projects the type of the field at `Index` in `Self` without regard for field
39+
/// privacy.
3940
///
4041
/// The `Index` parameter is any sort of handle that identifies the field; its
4142
/// definition is the obligation of the implementer.
@@ -529,6 +530,63 @@ macro_rules! assert_size_eq {
529530
}};
530531
}
531532

533+
/// Translates an identifier or tuple index into a numeric identifier.
534+
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
535+
#[macro_export]
536+
macro_rules! ident_id {
537+
($field:ident) => {
538+
$crate::util::macro_util::hash_name(stringify!($field))
539+
};
540+
($field:literal) => {
541+
$field
542+
};
543+
}
544+
545+
/// Computes the hash of a string.
546+
///
547+
/// NOTE(#2749) on hash collisions: This function's output only needs to be
548+
/// deterministic within a particular compilation. Thus, if a user ever reports
549+
/// a hash collision (very unlikely given the <= 16-byte special case), we can
550+
/// strengthen the hash function at that point and publish a new version. Since
551+
/// this is computed at compile time on small strings, we can easily use more
552+
/// expensive and higher-quality hash functions if need be.
553+
#[inline(always)]
554+
#[must_use]
555+
#[allow(clippy::as_conversions, clippy::indexing_slicing, clippy::arithmetic_side_effects)]
556+
pub const fn hash_name(name: &str) -> u128 {
557+
let name = name.as_bytes();
558+
559+
// We guarantee freedom from hash collisions between any two strings of
560+
// length 16 or less by having the hashes of such strings be equal to
561+
// their value. There is still a possibility that such strings will have
562+
// the same value as the hash of a string of length > 16.
563+
if name.len() <= size_of::<u128>() {
564+
let mut bytes = [0u8; 16];
565+
566+
let mut i = 0;
567+
while i < name.len() {
568+
bytes[i] = name[i];
569+
i += 1;
570+
}
571+
572+
return u128::from_ne_bytes(bytes);
573+
};
574+
575+
// An implementation of FxHasher, although returning a u128. Probably
576+
// not as strong as it could be, but probably more collision resistant
577+
// than normal 64-bit FxHasher.
578+
let mut hash = 0u128;
579+
let mut i = 0;
580+
while i < name.len() {
581+
// This is just FxHasher's `0x517cc1b727220a95` constant
582+
// concatenated back-to-back.
583+
const K: u128 = 0x517cc1b727220a95517cc1b727220a95;
584+
hash = (hash.rotate_left(5) ^ (name[i] as u128)).wrapping_mul(K);
585+
i += 1;
586+
}
587+
hash
588+
}
589+
532590
/// Is a given source a valid instance of `Dst`?
533591
///
534592
/// If so, returns `src` casted to a `Ptr<Dst, _>`. Otherwise returns `None`.

zerocopy-derive/src/enum.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66
// This file may not be copied, modified, or distributed except according to
77
// those terms.
88

9-
use proc_macro2::{Span, TokenStream};
9+
use proc_macro2::TokenStream;
1010
use quote::quote;
11-
use syn::{parse_quote, DataEnum, Error, Fields, Generics, Ident, Path};
11+
use syn::{
12+
parse_quote, spanned::Spanned as _, DataEnum, DeriveInput, Error, Fields, Generics, Ident, Path,
13+
};
1214

13-
use crate::{derive_try_from_bytes_inner, repr::EnumRepr, Trait};
15+
use crate::{
16+
derive_try_from_bytes_inner, repr::EnumRepr, DataExt, FieldBounds, ImplBlockBuilder, Trait,
17+
};
1418

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

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

253+
let has_fields =
254+
data.variants()
255+
.into_iter()
256+
.map(|(variant, fields)| {
257+
let variant_ident = &variant.unwrap().ident;
258+
fields.into_iter().map(move |(vis, ident, ty)| {
259+
// Rust does not presently support explicit visibility modifiers on
260+
// enum fields, but we guard against the possibility to ensure this
261+
// derive remains sound.
262+
assert!(matches!(vis, syn::Visibility::Inherited));
263+
ImplBlockBuilder::new(
264+
ast,
265+
data,
266+
Trait::HasField {
267+
variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
268+
// Since Rust does not presently support explicit visibility
269+
// modifiers on enum fields, any public type is suitable
270+
// here; we use `()`.
271+
field: parse_quote!(()),
272+
field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
273+
},
274+
FieldBounds::None,
275+
zerocopy_crate,
276+
).inner_extras(quote!{
277+
type Type = #ty;
278+
}).build()
279+
})
280+
})
281+
.flatten();
282+
248283
let match_arms = data.variants.iter().map(|variant| {
249284
let tag_ident = tag_ident(&variant.ident);
250285
let variant_struct_ident = variant_struct_ident(&variant.ident);
@@ -323,6 +358,8 @@ pub(crate) fn derive_is_bit_valid(
323358
variants: ___ZerocopyVariants #ty_generics,
324359
}
325360

361+
#(#has_fields)*
362+
326363
let tag = {
327364
// SAFETY:
328365
// - The provided cast addresses a subset of the bytes addressed

zerocopy-derive/src/ext.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
use proc_macro2::{Span, TokenStream};
1010
use quote::ToTokens;
11-
use syn::{Data, DataEnum, DataStruct, DataUnion, Field, Ident, Index, Type, Visibility};
11+
use syn::{Data, DataEnum, DataStruct, DataUnion, Field, Ident, Index, Type, Variant, Visibility};
1212

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

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

2626
fn tag(&self) -> Option<Ident>;
2727
}
@@ -35,7 +35,7 @@ impl DataExt for Data {
3535
}
3636
}
3737

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

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

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

74-
fn variants(&self) -> Vec<Vec<(&Visibility, TokenStream, &Type)>> {
75-
self.variants.iter().map(|var| map_fields(&var.fields)).collect()
74+
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
75+
self.variants.iter().map(|var| (Some(var), map_fields(&var.fields))).collect()
7676
}
7777

7878
fn tag(&self) -> Option<Ident> {
@@ -85,8 +85,8 @@ impl DataExt for DataUnion {
8585
map_fields(&self.fields.named)
8686
}
8787

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

9292
fn tag(&self) -> Option<Ident> {

0 commit comments

Comments
 (0)