Skip to content
Merged
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
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ glob = "=0.3.2"
itertools = "0.11"
rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }
rustversion = "1.0"
# More recent versions of `ryu` have an MSRV higher than ours.
ryu = "=1.0.20"
static_assertions = "1.1"
testutil = { path = "testutil" }
# Pinned to a specific version so that the version used for local development
Expand Down
48 changes: 48 additions & 0 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,54 @@ impl_for_transmute_from!(T: ?Sized + IntoBytes => IntoBytes for ManuallyDrop<T>[
const _: () = unsafe { unsafe_impl!(T: ?Sized + Unaligned => Unaligned for ManuallyDrop<T>) };
assert_unaligned!(ManuallyDrop<()>, ManuallyDrop<u8>);

const _: () = {
#[allow(
non_camel_case_types,
missing_copy_implementations,
missing_debug_implementations,
missing_docs
)]
pub enum value {}

// SAFETY: `ManuallyDrop<T>` has a field of type `T` at offset `0` without
// any safety invariants beyond those of `T`. Its existence is not
// explicitly documented, but it can be inferred; per [1] `ManuallyDrop<T>`
// has the same size and bit validity as `T`. This field is not literally
// public, but is effectively so; the field can be transparently:
//
// - initialized via `ManuallyDrop::new`
// - moved via `ManuallyDrop::into_inner`
// - referenced via `ManuallyDrop::deref`
// - exclusively referenced via `ManuallyDrop::deref_mut`
//
// We call this field `value`, both because that is both the name of this
// private field, and because it is the name it is referred to in the public
// documentation of `ManuallyDrop::new`, `ManuallyDrop::into_inner`,
// `ManuallyDrop::take` and `ManuallyDrop::drop`.
unsafe impl<T> HasField<value, 0, { crate::ident_id!(value) }> for ManuallyDrop<T> {
type Type = T;

#[inline]
fn only_derive_is_allowed_to_implement_this_trait()
where
Self: Sized,
{
}

#[inline(always)]
fn project(slf: PtrInner<'_, Self>) -> PtrInner<'_, T> {
// SAFETY: `ManuallyDrop<T>` has the same layout and bit validity as
// `T` [1].
//
// [1] Per https://doc.rust-lang.org/1.85.0/std/mem/struct.ManuallyDrop.html:
//
// `ManuallyDrop<T>` is guaranteed to have the same layout and bit
// validity as `T`
unsafe { slf.cast() }
}
}
};

impl_for_transmute_from!(T: ?Sized + TryFromBytes => TryFromBytes for Cell<T>[UnsafeCell<T>]);
impl_for_transmute_from!(T: ?Sized + FromZeros => FromZeros for Cell<T>[UnsafeCell<T>]);
impl_for_transmute_from!(T: ?Sized + FromBytes => FromBytes for Cell<T>[UnsafeCell<T>]);
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,8 @@ use core::{
use std::io;

use crate::pointer::invariant::{self, BecauseExclusive};
#[doc(hidden)]
pub use crate::pointer::PtrInner;
pub use crate::{
byte_slice::*,
byteorder::*,
Expand Down Expand Up @@ -1109,6 +1111,9 @@ pub unsafe trait HasField<Field, const VARIANT_ID: u128, const FIELD_ID: u128> {

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

/// Projects from `slf` to the field.
fn project(slf: PtrInner<'_, Self>) -> PtrInner<'_, Self::Type>;
}

/// Analyzes whether a type is [`FromZeros`].
Expand Down
12 changes: 11 additions & 1 deletion src/pointer/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::util::polyfills::NumExt as _;
use crate::{
layout::{CastType, MetadataCastError},
util::AsAddress,
AlignmentError, CastError, KnownLayout, MetadataOf, SizeError, SplitAt,
AlignmentError, CastError, HasField, KnownLayout, MetadataOf, SizeError, SplitAt,
};

mod _def {
Expand Down Expand Up @@ -196,6 +196,16 @@ impl<'a, T: ?Sized> PtrInner<'a, T> {
// then `A` is guaranteed to live for at least `'a`.
unsafe { PtrInner::new(ptr) }
}

/// Projects a field.
#[must_use]
#[inline(always)]
pub fn project<F, const VARIANT_ID: u128, const FIELD_ID: u128>(self) -> PtrInner<'a, T::Type>
where
T: HasField<F, VARIANT_ID, FIELD_ID>,
{
<T as HasField<F, VARIANT_ID, FIELD_ID>>::project(self)
}
}

#[allow(clippy::needless_lifetimes)]
Expand Down
103 changes: 72 additions & 31 deletions zerocopy-derive/src/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
parse_quote, spanned::Spanned as _, DataEnum, DeriveInput, Error, Fields, Generics, Ident, Path,
parse_quote, spanned::Spanned as _, DataEnum, DeriveInput, Error, Fields, Generics, Ident,
Index, Path,
};

use crate::{
derive_try_from_bytes_inner, repr::EnumRepr, DataExt, FieldBounds, ImplBlockBuilder, Trait,
derive_has_field_struct_union, 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
Expand Down Expand Up @@ -162,7 +164,18 @@ fn generate_variant_structs(
}
}

fn generate_variants_union(generics: &Generics, data: &DataEnum) -> TokenStream {
fn variants_union_field_ident(ident: &Ident) -> Ident {
let variant_ident_str = crate::ext::to_ident_str(ident);
// Field names are prefixed with `__field_` to prevent name collision
// with the `__nonempty` field.
Ident::new(&format!("__field_{}", variant_ident_str), ident.span())
}

fn generate_variants_union(
generics: &Generics,
data: &DataEnum,
zerocopy_crate: &Path,
) -> TokenStream {
let (_, ty_generics, _) = generics.split_for_impl();

let fields = data.variants.iter().filter_map(|variant| {
Expand All @@ -172,10 +185,7 @@ fn generate_variants_union(generics: &Generics, data: &DataEnum) -> TokenStream
return None;
}

// Field names are prefixed with `__field_` to prevent name collision
// with the `__nonempty` field.
let field_name_str = crate::ext::to_ident_str(&variant.ident);
let field_name = Ident::new(&format!("__field_{}", field_name_str), variant.ident.span());
let field_name = variants_union_field_ident(&variant.ident);
let variant_struct_ident = variant_struct_ident(&variant.ident);

Some(quote! {
Expand All @@ -185,7 +195,7 @@ fn generate_variants_union(generics: &Generics, data: &DataEnum) -> TokenStream
})
});

quote! {
let variants_union = parse_quote! {
#[repr(C)]
#[allow(non_snake_case)]
union ___ZerocopyVariants #generics {
Expand All @@ -197,6 +207,14 @@ fn generate_variants_union(generics: &Generics, data: &DataEnum) -> TokenStream
// affect the layout.
__nonempty: (),
}
};

let has_field =
derive_has_field_struct_union(&variants_union, &variants_union.data, zerocopy_crate);

quote! {
#variants_union
#has_field
}
}

Expand Down Expand Up @@ -246,18 +264,20 @@ pub(crate) fn derive_is_bit_valid(
};

let variant_structs = generate_variant_structs(enum_ident, generics, data, zerocopy_crate);
let variants_union = generate_variants_union(generics, data);
let variants_union = generate_variants_union(generics, data, zerocopy_crate);

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

let has_fields = data.variants().into_iter().flat_map(|(variant, fields)| {
let variant_ident = &variant.unwrap().ident;
let variants_union_field_ident = variants_union_field_ident(variant_ident);
let field: Box<syn::Type> = parse_quote!(());
fields.into_iter().map(move |(vis, ident, ty)| {
fields.into_iter().enumerate().map(move |(idx, (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));
let variant_struct_field_index = Index::from(idx + 1);
ImplBlockBuilder::new(
ast,
data,
Expand All @@ -274,6 +294,18 @@ pub(crate) fn derive_is_bit_valid(
)
.inner_extras(quote! {
type Type = #ty;

#[inline(always)]
fn project(slf: #zerocopy_crate::PtrInner<'_, Self>) -> #zerocopy_crate::PtrInner<'_, Self::Type> {
// SAFETY: By invariant on `___ZerocopyRawEnum`,
// `___ZerocopyRawEnum` has the same layout as `Self`.
let slf = unsafe { slf.cast::<___ZerocopyRawEnum #ty_generics>() };

slf.project::<_, 0, { #zerocopy_crate::ident_id!(variants) }>()
.project::<_, 0, { #zerocopy_crate::ident_id!(#variants_union_field_ident) }>()
.project::<_, 0, { #zerocopy_crate::ident_id!(value) }>()
.project::<_, 0, { #zerocopy_crate::ident_id!(#variant_struct_field_index) }>()
}
})
.build()
})
Expand Down Expand Up @@ -323,6 +355,22 @@ pub(crate) fn derive_is_bit_valid(
}
});

let raw_enum = parse_quote! {
#[repr(C)]
struct ___ZerocopyRawEnum #generics {
tag: ___ZerocopyOuterTag,
variants: ___ZerocopyVariants #ty_generics,
}
};

let raw_enum_projections =
derive_has_field_struct_union(&raw_enum, &raw_enum.data, zerocopy_crate);

let raw_enum = quote! {
#raw_enum
#raw_enum_projections
};

Ok(quote! {
// SAFETY: We use `is_bit_valid` to validate that the bit pattern of the
// enum's tag corresponds to one of the enum's discriminants. Then, we
Expand Down Expand Up @@ -351,11 +399,7 @@ pub(crate) fn derive_is_bit_valid(

#variants_union

#[repr(C)]
struct ___ZerocopyRawEnum #generics {
tag: ___ZerocopyOuterTag,
variants: ___ZerocopyVariants #ty_generics,
}
#raw_enum

#(#has_fields)*

Expand Down Expand Up @@ -399,26 +443,23 @@ pub(crate) fn derive_is_bit_valid(
// invariant from `p`, so we re-assert that all of the bytes are
// initialized.
let raw_enum = unsafe { raw_enum.assume_initialized() };

// SAFETY:
// - This projection returns a subfield of `this` using
// `addr_of_mut!`.
// - Because the subfield pointer is derived from `this`, it has the
// same provenance.
// - This projection returns a subfield of `raw_enum` using
// `project`.
// - Because the subfield pointer is derived from `raw_enum`, it has
// the same provenance.
// - The locations of `UnsafeCell`s in the subfield match the
// locations of `UnsafeCell`s in `this`. This is because the
// locations of `UnsafeCell`s in `raw_enum`. This is because the
// subfield pointer just points to a smaller portion of the
// overall struct.
let project = #zerocopy_crate::pointer::PtrInner::project::<
_,
0,
{ #zerocopy_crate::ident_id!(variants) }
>;
let variants = unsafe {
use #zerocopy_crate::pointer::PtrInner;
raw_enum.cast_unsized_unchecked(|p: PtrInner<'_, ___ZerocopyRawEnum #ty_generics>| {
let p = p.as_non_null().as_ptr();
let ptr = core_reexport::ptr::addr_of_mut!((*p).variants);
// SAFETY: `ptr` is a projection into `p`, which is
// `NonNull`, and guaranteed not to wrap around the address
// space. Thus, `ptr` cannot be null.
let ptr = unsafe { core_reexport::ptr::NonNull::new_unchecked(ptr) };
unsafe { PtrInner::new(ptr) }
})
raw_enum.cast_unsized_unchecked(project)
};

#[allow(non_upper_case_globals)]
Expand Down
Loading
Loading