diff --git a/Cargo.toml b/Cargo.toml index 98965bf..ee1ccca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bytemuck" description = "A crate for mucking around with piles of bytes." -version = "1.17.1" +version = "1.19.0" authors = ["Lokathor "] repository = "https://github.com/Lokathor/bytemuck" readme = "README.md" @@ -13,10 +13,16 @@ exclude = ["/pedantic.bat"] [features] # In v2 we'll fix these names to be more "normal". + +# Enable deriving the various `bytemuck` traits. derive = ["bytemuck_derive"] +# Enable features requiring items from `extern crate alloc`. extern_crate_alloc = [] +# Enable features requiring items from `extern crate std`. extern_crate_std = ["extern_crate_alloc"] +# Implement `Zeroable` for `MaybeUninit`. zeroable_maybe_uninit = [] +# Implement `Zeroable` for `std::sync::atomic` types. zeroable_atomics = [] # All MSRV notes below are GUIDELINES and future versions may require even more @@ -44,8 +50,32 @@ const_zeroed = [] # MSRV 1.75.0: support const `zeroed()` # Do not use if you can avoid it, because this is **unsound**!!!! unsound_ptr_pod_impl = [] -# NOT SEMVER SUPPORTED! TEMPORARY ONLY! +# MSRV 1.46.0: adds the `#[track_caller]` attribute to functions which may panic +track_caller = [] + +# Enables all features that are both sound and supported on the latest stable +# version of Rust, with the exception of `extern_crate_alloc` and +# `extern_crate_std`. +# Note: Enabling this feature opts out of any MSRV guarantees! +latest_stable_rust = [ + # Keep this list sorted. + "aarch64_simd", + "align_offset", + "const_zeroed", + "derive", + "min_const_generics", + "must_cast", + "track_caller", + "wasm_simd", + "zeroable_atomics", + "zeroable_maybe_uninit", +] + +# ALL FEATURES BELOW THIS ARE NOT SEMVER SUPPORTED! TEMPORARY ONLY! + +# Enable support for `std::simd` types. nightly_portable_simd = [] +# Enable support for unstable `std::arch` types (such as the AVX512 types). nightly_stdsimd = [] # Enable `f16` and `f128` nightly_float = [] @@ -63,29 +93,15 @@ unexpected_cfgs = { level = "deny", check-cfg = ['cfg(target_arch, values("spirv # Note(Lokathor): Don't use all-features or it would use `unsound_ptr_pod_impl` too. features = [ "nightly_docs", - "derive", + "latest_stable_rust", "extern_crate_alloc", "extern_crate_std", - "zeroable_maybe_uninit", - "zeroable_atomics", - "min_const_generics", - "wasm_simd", - "must_cast", - "transparentwrapper_extra", - "const_zeroed", ] [package.metadata.playground] # Note(Lokathor): Don't use all-features or it would use `unsound_ptr_pod_impl` too. features = [ - "derive", + "latest_stable_rust", "extern_crate_alloc", "extern_crate_std", - "zeroable_maybe_uninit", - "zeroable_atomics", - "min_const_generics", - "wasm_simd", - "must_cast", - "transparentwrapper_extra", - "const_zeroed", ] diff --git a/changelog.md b/changelog.md index 3ca93ae..153ff65 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # `bytemuck` changelog +## 1.19 + +* Adds the `#[track_caller]` attribute to functions which may panic. + +## 1.18 + +* Adds the `latest_stable_rust` cargo feature, which is a blanket feature that turns all other features on that are both sound and compatible with Stable rust. + ## 1.17.1 * Adds `#[repr(C)]` to the `union Transmute` type that's used internally diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 9011205..5acc887 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bytemuck_derive" description = "derive proc-macros for `bytemuck`" -version = "1.7.1" +version = "1.8.0" authors = ["Lokathor "] repository = "https://github.com/Lokathor/bytemuck" readme = "README.md" diff --git a/derive/changelog.md b/derive/changelog.md index b2b1b09..507817a 100644 --- a/derive/changelog.md +++ b/derive/changelog.md @@ -1,6 +1,10 @@ ## `bytemuck_derive` changelog +## 1.8 + +* [#257](https://github.com/Lokathor/bytemuck/pull/257): Allows deriving Zeroable on some enums. + ## 1.7.1 * Adds the `bytemuck` attribute to the `NoUninit` derive, allowing it to be used when re-exported. diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 806a72f..26ecfc1 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -114,14 +114,26 @@ pub fn derive_anybitpattern( proc_macro::TokenStream::from(expanded) } -/// Derive the `Zeroable` trait for a struct +/// Derive the `Zeroable` trait for a type. /// -/// The macro ensures that the struct follows all the the safety requirements +/// The macro ensures that the type follows all the the safety requirements /// for the `Zeroable` trait. /// -/// The following constraints need to be satisfied for the macro to succeed +/// The following constraints need to be satisfied for the macro to succeed on a +/// struct: +/// +/// - All fields in the struct must implement `Zeroable` +/// +/// The following constraints need to be satisfied for the macro to succeed on +/// an enum: /// -/// - All fields in the struct must to implement `Zeroable` +/// - The enum has an explicit `#[repr(Int)]`, `#[repr(C)]`, or `#[repr(C, +/// Int)]`. +/// - The enum has a variant with discriminant 0 (explicitly or implicitly). +/// - All fields in the variant with discriminant 0 (if any) must implement +/// `Zeroable` +/// +/// The macro always succeeds on unions. /// /// ## Example /// @@ -134,6 +146,23 @@ pub fn derive_anybitpattern( /// b: u16, /// } /// ``` +/// ```rust +/// # use bytemuck_derive::{Zeroable}; +/// #[derive(Copy, Clone, Zeroable)] +/// #[repr(i32)] +/// enum Values { +/// A = 0, +/// B = 1, +/// C = 2, +/// } +/// #[derive(Clone, Zeroable)] +/// #[repr(C)] +/// enum Implicit { +/// A(bool, u8, char), +/// B(String), +/// C(std::num::NonZeroU8), +/// } +/// ``` /// /// # Custom bounds /// @@ -157,6 +186,18 @@ pub fn derive_anybitpattern( /// /// AlwaysZeroable::::zeroed(); /// ``` +/// ```rust +/// # use bytemuck::{Zeroable}; +/// #[derive(Copy, Clone, Zeroable)] +/// #[repr(u8)] +/// #[zeroable(bound = "")] +/// enum MyOption { +/// None, +/// Some(T), +/// } +/// +/// assert!(matches!(MyOption::::zeroed(), MyOption::None)); +/// ``` /// /// ```rust,compile_fail /// # use bytemuck::Zeroable; @@ -407,7 +448,8 @@ pub fn derive_byte_eq( let input = parse_macro_input!(input as DeriveInput); let crate_name = bytemuck_crate_name(&input); let ident = input.ident; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let (impl_generics, ty_generics, where_clause) = + input.generics.split_for_impl(); proc_macro::TokenStream::from(quote! { impl #impl_generics ::core::cmp::PartialEq for #ident #ty_generics #where_clause { @@ -460,7 +502,8 @@ pub fn derive_byte_hash( let input = parse_macro_input!(input as DeriveInput); let crate_name = bytemuck_crate_name(&input); let ident = input.ident; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let (impl_generics, ty_generics, where_clause) = + input.generics.split_for_impl(); proc_macro::TokenStream::from(quote! { impl #impl_generics ::core::hash::Hash for #ident #ty_generics #where_clause { @@ -569,19 +612,18 @@ fn derive_marker_trait_inner( .flatten() .collect::>(); - let predicates = &mut input.generics.make_where_clause().predicates; - - predicates.extend(explicit_bounds); - - let fields = match &input.data { - syn::Data::Struct(syn::DataStruct { fields, .. }) => fields.clone(), - syn::Data::Union(_) => { + let fields = match (Trait::perfect_derive_fields(&input), &input.data) { + (Some(fields), _) => fields, + (None, syn::Data::Struct(syn::DataStruct { fields, .. })) => { + fields.clone() + } + (None, syn::Data::Union(_)) => { return Err(syn::Error::new_spanned( trait_, &"perfect derive is not supported for unions", )); } - syn::Data::Enum(_) => { + (None, syn::Data::Enum(_)) => { return Err(syn::Error::new_spanned( trait_, &"perfect derive is not supported for enums", @@ -589,6 +631,10 @@ fn derive_marker_trait_inner( } }; + let predicates = &mut input.generics.make_where_clause().predicates; + + predicates.extend(explicit_bounds); + for field in fields { let ty = field.ty; predicates.push(syn::parse_quote!( diff --git a/derive/src/traits.rs b/derive/src/traits.rs index 89fc6c4..02a0e41 100644 --- a/derive/src/traits.rs +++ b/derive/src/traits.rs @@ -2,7 +2,7 @@ use std::{cmp, convert::TryFrom}; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; -use quote::{quote, quote_spanned, ToTokens}; +use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream, Parser}, punctuated::Punctuated, @@ -44,6 +44,14 @@ pub trait Derivable { fn explicit_bounds_attribute_name() -> Option<&'static str> { None } + + /// If this trait has a custom meaning for "perfect derive", this function + /// should be overridden to return `Some`. + /// + /// The default is "the fields of a struct; unions and enums not supported". + fn perfect_derive_fields(_input: &DeriveInput) -> Option { + None + } } pub struct Pod; @@ -76,8 +84,11 @@ impl Derivable for Pod { } else { None }; - let assert_fields_are_pod = - generate_fields_are_trait(input, Self::ident(input, crate_name)?)?; + let assert_fields_are_pod = generate_fields_are_trait( + input, + None, + Self::ident(input, crate_name)?, + )?; Ok(quote!( #assert_no_padding @@ -118,7 +129,7 @@ impl Derivable for AnyBitPattern { match &input.data { Data::Union(_) => Ok(quote!()), // unions are always `AnyBitPattern` Data::Struct(_) => { - generate_fields_are_trait(input, Self::ident(input, crate_name)?) + generate_fields_are_trait(input, None, Self::ident(input, crate_name)?) } Data::Enum(_) => { bail!("Deriving AnyBitPattern is not supported for enums") @@ -129,6 +140,21 @@ impl Derivable for AnyBitPattern { pub struct Zeroable; +/// Helper function to get the variant with discriminant zero (implicit or +/// explicit). +fn get_zero_variant(enum_: &DataEnum) -> Result> { + let iter = VariantDiscriminantIterator::new(enum_.variants.iter()); + let mut zero_variant = None; + for res in iter { + let (discriminant, variant) = res?; + if discriminant == 0 { + zero_variant = Some(variant); + break; + } + } + Ok(zero_variant) +} + impl Derivable for Zeroable { fn ident(_: &DeriveInput, crate_name: &TokenStream) -> Result { Ok(syn::parse_quote!(#crate_name::Zeroable)) @@ -138,27 +164,16 @@ impl Derivable for Zeroable { let repr = get_repr(attributes)?; match ty { Data::Struct(_) => Ok(()), - Data::Enum(DataEnum { variants, .. }) => { - if !repr.repr.is_integer() { - bail!("Zeroable requires the enum to be an explicit #[repr(Int)]") - } - - if variants.iter().any(|variant| !variant.fields.is_empty()) { - bail!("Only fieldless enums are supported for Zeroable") + Data::Enum(_) => { + if !matches!( + repr.repr, + Repr::C | Repr::Integer(_) | Repr::CWithDiscriminant(_) + ) { + bail!("Zeroable requires the enum to be an explicit #[repr(Int)] and/or #[repr(C)]") } - let iter = VariantDiscriminantIterator::new(variants.iter()); - let mut has_zero_variant = false; - for res in iter { - let discriminant = res?; - if discriminant == 0 { - has_zero_variant = true; - break; - } - } - if !has_zero_variant { - bail!("No variant's discriminant is 0") - } + // We ensure there is a zero variant in `asserts`, since it is needed + // there anyway. Ok(()) } @@ -172,15 +187,40 @@ impl Derivable for Zeroable { match &input.data { Data::Union(_) => Ok(quote!()), // unions are always `Zeroable` Data::Struct(_) => { - generate_fields_are_trait(input, Self::ident(input, crate_name)?) + generate_fields_are_trait(input, None, Self::ident(input, crate_name)?) + } + Data::Enum(enum_) => { + let zero_variant = get_zero_variant(enum_)?; + + if zero_variant.is_none() { + bail!("No variant's discriminant is 0") + }; + + generate_fields_are_trait( + input, + zero_variant, + Self::ident(input, crate_name)?, + ) } - Data::Enum(_) => Ok(quote!()), } } fn explicit_bounds_attribute_name() -> Option<&'static str> { Some("zeroable") } + + fn perfect_derive_fields(input: &DeriveInput) -> Option { + match &input.data { + Data::Struct(struct_) => Some(struct_.fields.clone()), + Data::Enum(enum_) => { + // We handle `Err` returns from `get_zero_variant` in `asserts`, so it's + // fine to just ignore them here and return `None`. + // Otherwise, we clone the `fields` of the zero variant (if any). + Some(get_zero_variant(enum_).ok()??.fields.clone()) + } + Data::Union(_) => None, + } + } } pub struct NoUninit; @@ -216,8 +256,11 @@ impl Derivable for NoUninit { match &input.data { Data::Struct(DataStruct { .. }) => { let assert_no_padding = generate_assert_no_padding(&input)?; - let assert_fields_are_no_padding = - generate_fields_are_trait(&input, Self::ident(input, crate_name)?)?; + let assert_fields_are_no_padding = generate_fields_are_trait( + &input, + None, + Self::ident(input, crate_name)?, + )?; Ok(quote!( #assert_no_padding @@ -282,13 +325,16 @@ impl Derivable for CheckedBitPattern { match &input.data { Data::Struct(DataStruct { .. }) => { - let assert_fields_are_maybe_pod = - generate_fields_are_trait(&input, Self::ident(input, crate_name)?)?; + let assert_fields_are_maybe_pod = generate_fields_are_trait( + &input, + None, + Self::ident(input, crate_name)?, + )?; Ok(assert_fields_are_maybe_pod) } - Data::Enum(_) => Ok(quote!()), /* nothing needed, already guaranteed - * OK by NoUninit */ + // nothing needed, already guaranteed OK by NoUninit. + Data::Enum(_) => Ok(quote!()), Data::Union(_) => bail!("Internal error in CheckedBitPattern derive"), /* shouldn't be possible since we already error in attribute check for this case */ } } @@ -439,16 +485,16 @@ impl Derivable for Contiguous { )); } - let mut variants_with_discriminator = + let mut variants_with_discriminant = VariantDiscriminantIterator::new(variants); - let (min, max, count) = variants_with_discriminator.try_fold( - (i64::max_value(), i64::min_value(), 0), + let (min, max, count) = variants_with_discriminant.try_fold( + (i128::MAX, i128::MIN, 0), |(min, max, count), res| { - let discriminator = res?; + let (discriminant, _variant) = res?; Ok::<_, Error>(( - i64::min(min, discriminator), - i64::max(max, discriminator), + i128::min(min, discriminant), + i128::max(max, discriminant), count + 1, )) }, @@ -505,11 +551,21 @@ fn get_struct_fields(input: &DeriveInput) -> Result<&Fields> { } } -fn get_fields(input: &DeriveInput) -> Result { +/// Extract the `Fields` off a `DeriveInput`, or, in the `enum` case, off +/// those of the `enum_variant`, when provided (e.g., for `Zeroable`). +/// +/// We purposely allow not providing an `enum_variant` for cases where +/// the caller wants to reject supporting `enum`s (e.g., `NoPadding`). +fn get_fields( + input: &DeriveInput, enum_variant: Option<&Variant>, +) -> Result { match &input.data { Data::Struct(DataStruct { fields, .. }) => Ok(fields.clone()), Data::Union(DataUnion { fields, .. }) => Ok(Fields::Named(fields.clone())), - Data::Enum(_) => bail!("deriving this trait is not supported for enums"), + Data::Enum(_) => match enum_variant { + Some(variant) => Ok(variant.fields.clone()), + None => bail!("deriving this trait is not supported for enums"), + }, } } @@ -596,19 +652,19 @@ fn generate_checked_bit_pattern_enum_without_fields( VariantDiscriminantIterator::new(variants.iter()); let (min, max, count) = variants_with_discriminant.try_fold( - (i64::max_value(), i64::min_value(), 0), + (i128::MAX, i128::MIN, 0), |(min, max, count), res| { - let discriminant = res?; + let (discriminant, _variant) = res?; Ok::<_, Error>(( - i64::min(min, discriminant), - i64::max(max, discriminant), + i128::min(min, discriminant), + i128::max(max, discriminant), count + 1, )) }, )?; let check = if count == 0 { - quote_spanned!(span => false) + quote!(false) } else if max - min == count - 1 { // contiguous range let min_lit = LitInt::new(&format!("{}", min), span); @@ -617,16 +673,17 @@ fn generate_checked_bit_pattern_enum_without_fields( quote!(*bits >= #min_lit && *bits <= #max_lit) } else { // not contiguous range, check for each - let variant_lits = VariantDiscriminantIterator::new(variants.iter()) - .map(|res| { - let variant = res?; - Ok(LitInt::new(&format!("{}", variant), span)) - }) - .collect::>>()?; + let variant_discriminant_lits = + VariantDiscriminantIterator::new(variants.iter()) + .map(|res| { + let (discriminant, _variant) = res?; + Ok(LitInt::new(&format!("{}", discriminant), span)) + }) + .collect::>>()?; // count is at least 1 - let first = &variant_lits[0]; - let rest = &variant_lits[1..]; + let first = &variant_discriminant_lits[0]; + let rest = &variant_discriminant_lits[1..]; quote!(matches!(*bits, #first #(| #rest )*)) }; @@ -720,7 +777,7 @@ fn generate_checked_bit_pattern_enum_with_fields( .zip(VariantDiscriminantIterator::new(variants.iter())) .zip(variants.iter()) .map(|((variant_struct_ident, discriminant), v)| -> Result<_> { - let discriminant = discriminant?; + let (discriminant, _variant) = discriminant?; let discriminant = LitInt::new(&discriminant.to_string(), v.span()); let ident = &v.ident; Ok(quote! { @@ -850,7 +907,7 @@ fn generate_checked_bit_pattern_enum_with_fields( .zip(VariantDiscriminantIterator::new(variants.iter())) .zip(variants.iter()) .map(|((variant_struct_ident, discriminant), v)| -> Result<_> { - let discriminant = discriminant?; + let (discriminant, _variant) = discriminant?; let discriminant = LitInt::new(&discriminant.to_string(), v.span()); let ident = &v.ident; Ok(quote! { @@ -905,21 +962,20 @@ fn generate_checked_bit_pattern_enum_with_fields( /// is equal to the sum of the size of it's fields fn generate_assert_no_padding(input: &DeriveInput) -> Result { let struct_type = &input.ident; - let span = input.ident.span(); - let fields = get_fields(input)?; + let enum_variant = None; // `no padding` check is not supported for `enum`s yet. + let fields = get_fields(input, enum_variant)?; let mut field_types = get_field_types(&fields); let size_sum = if let Some(first) = field_types.next() { - let size_first = quote_spanned!(span => ::core::mem::size_of::<#first>()); - let size_rest = - quote_spanned!(span => #( + ::core::mem::size_of::<#field_types>() )*); + let size_first = quote!(::core::mem::size_of::<#first>()); + let size_rest = quote!(#( + ::core::mem::size_of::<#field_types>() )*); - quote_spanned!(span => #size_first #size_rest) + quote!(#size_first #size_rest) } else { - quote_spanned!(span => 0) + quote!(0) }; - Ok(quote_spanned! {span => const _: fn() = || { + Ok(quote! {const _: fn() = || { #[doc(hidden)] struct TypeWithoutPadding([u8; #size_sum]); let _ = ::core::mem::transmute::<#struct_type, TypeWithoutPadding>; @@ -928,14 +984,13 @@ fn generate_assert_no_padding(input: &DeriveInput) -> Result { /// Check that all fields implement a given trait fn generate_fields_are_trait( - input: &DeriveInput, trait_: syn::Path, + input: &DeriveInput, enum_variant: Option<&Variant>, trait_: syn::Path, ) -> Result { let (impl_generics, _ty_generics, where_clause) = input.generics.split_for_impl(); - let fields = get_fields(input)?; - let span = input.span(); + let fields = get_fields(input, enum_variant)?; let field_types = get_field_types(&fields); - Ok(quote_spanned! {span => #(const _: fn() = || { + Ok(quote! {#(const _: fn() = || { #[allow(clippy::missing_const_for_fn)] #[doc(hidden)] fn check #impl_generics () #where_clause { @@ -1186,7 +1241,7 @@ fn enum_has_fields<'a>( struct VariantDiscriminantIterator<'a, I: Iterator + 'a> { inner: I, - last_value: i64, + last_value: i128, } impl<'a, I: Iterator + 'a> @@ -1200,7 +1255,7 @@ impl<'a, I: Iterator + 'a> impl<'a, I: Iterator + 'a> Iterator for VariantDiscriminantIterator<'a, I> { - type Item = Result; + type Item = Result<(i128, &'a Variant)>; fn next(&mut self) -> Option { let variant = self.inner.next()?; @@ -1212,14 +1267,38 @@ impl<'a, I: Iterator + 'a> Iterator }; self.last_value = discriminant_value; } else { - self.last_value += 1; + // If this wraps, then either: + // 1. the enum is using repr(u128), so wrapping is correct + // 2. the enum is using repr(i<=128 or u<128), so the compiler will + // already emit a "wrapping discriminant" E0370 error. + self.last_value = self.last_value.wrapping_add(1); + // Static assert that there is no integer repr > 128 bits. If that + // changes, the above comment is inaccurate and needs to be updated! + // FIXME(zachs18): maybe should also do something to ensure `isize::BITS + // <= 128`? + if let Some(repr) = None:: { + match repr { + IntegerRepr::U8 + | IntegerRepr::I8 + | IntegerRepr::U16 + | IntegerRepr::I16 + | IntegerRepr::U32 + | IntegerRepr::I32 + | IntegerRepr::U64 + | IntegerRepr::I64 + | IntegerRepr::I128 + | IntegerRepr::U128 + | IntegerRepr::Usize + | IntegerRepr::Isize => (), + } + } } - Some(Ok(self.last_value)) + Some(Ok((self.last_value, variant))) } } -fn parse_int_expr(expr: &Expr) -> Result { +fn parse_int_expr(expr: &Expr) -> Result { match expr { Expr::Unary(ExprUnary { op: UnOp::Neg(_), expr, .. }) => { parse_int_expr(expr).map(|int| -int) diff --git a/derive/tests/basic.rs b/derive/tests/basic.rs index dfb1ff6..b04bfc6 100644 --- a/derive/tests/basic.rs +++ b/derive/tests/basic.rs @@ -1,8 +1,9 @@ #![allow(dead_code)] +#![deny(clippy::allow_attributes)] use bytemuck::{ - AnyBitPattern, CheckedBitPattern, Contiguous, NoUninit, Pod, - TransparentWrapper, Zeroable, checked::CheckedCastError, + checked::CheckedCastError, AnyBitPattern, CheckedBitPattern, Contiguous, + NoUninit, Pod, TransparentWrapper, Zeroable, }; use std::marker::{PhantomData, PhantomPinned}; @@ -58,6 +59,45 @@ enum ZeroEnum { C = 2, } +#[derive(Zeroable)] +#[repr(u8)] +enum BasicFieldfulZeroEnum { + A(u8) = 0, + B = 1, + C(String) = 2, +} + +#[derive(Zeroable)] +#[repr(C)] +enum ReprCFieldfulZeroEnum { + A(u8), + B(Box<[u8]>), + C, +} + +#[derive(Zeroable)] +#[repr(C, i32)] +enum ReprCIntFieldfulZeroEnum { + B(String) = 1, + A(u8, bool, char) = 0, + C = 2, +} + +#[derive(Zeroable)] +#[repr(i32)] +enum GenericFieldfulZeroEnum { + A(Box) = 1, + B(T, T) = 0, +} + +#[derive(Zeroable)] +#[repr(i32)] +#[zeroable(bound = "")] +enum GenericCustomBoundFieldfulZeroEnum { + A(Option>), + B(String), +} + #[derive(TransparentWrapper)] #[repr(transparent)] struct TransparentSingle { @@ -202,8 +242,10 @@ enum CheckedBitPatternTransparentEnumWithFields { } // size 24, align 8. -// first byte always the u8 discriminant, then 7 bytes of padding until the payload union since the align of the payload -// is the greatest of the align of all the variants, which is 8 (from CheckedBitPatternCDefaultDiscriminantEnumWithFields) +// first byte always the u8 discriminant, then 7 bytes of padding until the +// payload union since the align of the payload is the greatest of the align of +// all the variants, which is 8 (from +// CheckedBitPatternCDefaultDiscriminantEnumWithFields) #[derive(Debug, Clone, Copy, CheckedBitPattern, PartialEq, Eq)] #[repr(C, u8)] enum CheckedBitPatternEnumNested { @@ -388,52 +430,68 @@ fn checkedbitpattern_nested_enum_with_fields() { // first we'll check variantA, nested variant A let pod = Align8Bytes([ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // byte 0 discriminant = 0 = variant A, bytes 1-7 irrelevant padding. - 0x00, 0x00, 0x00, 0x00, 0xcc, 0x55, 0x55, 0xcc, // bytes 8-15 are the nested CheckedBitPatternCEnumWithFields, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, // byte 0 discriminant = 0 = variant A, bytes 1-7 irrelevant padding. + 0x00, 0x00, 0x00, 0x00, 0xcc, 0x55, 0x55, + 0xcc, // bytes 8-15 are the nested CheckedBitPatternCEnumWithFields, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // bytes 16-23 padding ]); - let value = bytemuck::checked::from_bytes::< - CheckedBitPatternEnumNested, - >(&pod.0); - assert_eq!(value, &CheckedBitPatternEnumNested::A(CheckedBitPatternCEnumWithFields::A(0xcc5555cc))); + let value = + bytemuck::checked::from_bytes::(&pod.0); + assert_eq!( + value, + &CheckedBitPatternEnumNested::A(CheckedBitPatternCEnumWithFields::A( + 0xcc5555cc + )) + ); // next we'll check invalid first discriminant fails let pod = Align8Bytes([ - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // byte 0 discriminant = 2 = invalid, bytes 1-7 padding - 0x00, 0x00, 0x00, 0x00, 0xcc, 0x55, 0x55, 0xcc, // bytes 8-15 are the nested CheckedBitPatternCEnumWithFields = A, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, // byte 0 discriminant = 2 = invalid, bytes 1-7 padding + 0x00, 0x00, 0x00, 0x00, 0xcc, 0x55, 0x55, + 0xcc, // bytes 8-15 are the nested CheckedBitPatternCEnumWithFields = A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // bytes 16-23 padding ]); - let result = bytemuck::checked::try_from_bytes::< - CheckedBitPatternEnumNested, - >(&pod.0); + let result = + bytemuck::checked::try_from_bytes::(&pod.0); assert_eq!(result, Err(CheckedCastError::InvalidBitPattern)); - // next we'll check variant B, nested variant B let pod = Align8Bytes([ - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // byte 0 discriminant = 1 = variant B, bytes 1-7 padding - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // bytes 8-15 is C int size discriminant of CheckedBitPatternCDefaultDiscrimimantEnumWithFields, 1 (LE byte order) = variant B - 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xcc, // bytes 16-13 is the data contained in nested variant B + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, // byte 0 discriminant = 1 = variant B, bytes 1-7 padding + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, /* bytes 8-15 is C int size discriminant of + * CheckedBitPatternCDefaultDiscrimimantEnumWithFields, 1 (LE byte + * order) = variant B */ + 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0xcc, // bytes 16-13 is the data contained in nested variant B ]); - let value = bytemuck::checked::from_bytes::< - CheckedBitPatternEnumNested, - >(&pod.0); + let value = + bytemuck::checked::from_bytes::(&pod.0); assert_eq!( value, - &CheckedBitPatternEnumNested::B(CheckedBitPatternCDefaultDiscriminantEnumWithFields::B { - c: 0xcc555555555555cc - }) + &CheckedBitPatternEnumNested::B( + CheckedBitPatternCDefaultDiscriminantEnumWithFields::B { + c: 0xcc555555555555cc + } + ) ); // finally we'll check variant B, nested invalid discriminant let pod = Align8Bytes([ - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1 discriminant = variant B, bytes 1-7 padding - 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // bytes 8-15 is C int size discriminant of CheckedBitPatternCDefaultDiscrimimantEnumWithFields, 0x08 is invalid - 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xcc, // bytes 16-13 is the data contained in nested variant B + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, // 1 discriminant = variant B, bytes 1-7 padding + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, /* bytes 8-15 is C int size discriminant of + * CheckedBitPatternCDefaultDiscrimimantEnumWithFields, 0x08 is + * invalid */ + 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0xcc, // bytes 16-13 is the data contained in nested variant B ]); - let result = bytemuck::checked::try_from_bytes::< - CheckedBitPatternEnumNested, - >(&pod.0); + let result = + bytemuck::checked::try_from_bytes::(&pod.0); assert_eq!(result, Err(CheckedCastError::InvalidBitPattern)); } #[test] @@ -457,4 +515,3 @@ use bytemuck as reexport_name; #[bytemuck(crate = "reexport_name")] #[repr(C)] struct Issue93 {} - diff --git a/src/allocation.rs b/src/allocation.rs index 8c5ac80..817e23c 100644 --- a/src/allocation.rs +++ b/src/allocation.rs @@ -891,6 +891,7 @@ pub fn box_bytes_of(input: Box) -> BoxBytes { /// This is [`try_from_box_bytes`] but will panic on error and the input will be /// dropped. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn from_box_bytes( input: BoxBytes, ) -> Box { diff --git a/src/checked.rs b/src/checked.rs index 3299ce8..05e0137 100644 --- a/src/checked.rs +++ b/src/checked.rs @@ -292,7 +292,7 @@ pub fn try_pod_read_unaligned( } } -/// Try to cast `T` into `U`. +/// Try to cast `A` into `B`. /// /// Note that for this particular type of cast, alignment isn't a factor. The /// input value is semantically copied into the function and then returned to a @@ -316,7 +316,7 @@ pub fn try_cast( } } -/// Try to convert a `&T` into `&U`. +/// Try to convert a `&A` into `&B`. /// /// ## Failure /// @@ -336,7 +336,7 @@ pub fn try_cast_ref( } } -/// Try to convert a `&mut T` into `&mut U`. +/// Try to convert a `&mut A` into `&mut B`. /// /// As [`try_cast_ref`], but `mut`. #[inline] @@ -413,6 +413,7 @@ pub fn try_cast_slice_mut< /// /// This is [`try_from_bytes`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn from_bytes(s: &[u8]) -> &T { match try_from_bytes(s) { Ok(t) => t, @@ -426,6 +427,7 @@ pub fn from_bytes(s: &[u8]) -> &T { /// /// This is [`try_from_bytes_mut`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn from_bytes_mut(s: &mut [u8]) -> &mut T { match try_from_bytes_mut(s) { Ok(t) => t, @@ -438,6 +440,7 @@ pub fn from_bytes_mut(s: &mut [u8]) -> &mut T { /// ## Panics /// * This is like `try_pod_read_unaligned` but will panic on failure. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn pod_read_unaligned(bytes: &[u8]) -> T { match try_pod_read_unaligned(bytes) { Ok(t) => t, @@ -445,12 +448,13 @@ pub fn pod_read_unaligned(bytes: &[u8]) -> T { } } -/// Cast `T` into `U` +/// Cast `A` into `B` /// /// ## Panics /// /// * This is like [`try_cast`], but will panic on a size mismatch. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn cast(a: A) -> B { match try_cast(a) { Ok(t) => t, @@ -458,12 +462,13 @@ pub fn cast(a: A) -> B { } } -/// Cast `&mut T` into `&mut U`. +/// Cast `&mut A` into `&mut B`. /// /// ## Panics /// /// This is [`try_cast_mut`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn cast_mut< A: NoUninit + AnyBitPattern, B: NoUninit + CheckedBitPattern, @@ -476,12 +481,13 @@ pub fn cast_mut< } } -/// Cast `&T` into `&U`. +/// Cast `&A` into `&B`. /// /// ## Panics /// /// This is [`try_cast_ref`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn cast_ref(a: &A) -> &B { match try_cast_ref(a) { Ok(t) => t, @@ -495,6 +501,7 @@ pub fn cast_ref(a: &A) -> &B { /// /// This is [`try_cast_slice`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn cast_slice(a: &[A]) -> &[B] { match try_cast_slice(a) { Ok(t) => t, @@ -502,12 +509,13 @@ pub fn cast_slice(a: &[A]) -> &[B] { } } -/// Cast `&mut [T]` into `&mut [U]`. +/// Cast `&mut [A]` into `&mut [B]`. /// /// ## Panics /// /// This is [`try_cast_slice_mut`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn cast_slice_mut< A: NoUninit + AnyBitPattern, B: NoUninit + CheckedBitPattern, diff --git a/src/contiguous.rs b/src/contiguous.rs index 91308d0..0db3b48 100644 --- a/src/contiguous.rs +++ b/src/contiguous.rs @@ -118,6 +118,7 @@ pub unsafe trait Contiguous: Copy + 'static { /// This is undefined behavior regardless, so it could have been the nasal /// demons at that point anyway ;). #[inline] + #[cfg_attr(feature = "track_caller", track_caller)] fn from_integer(value: Self::Int) -> Option { // Guard against an illegal implementation of Contiguous. Annoyingly we // can't rely on `transmute` to do this for us (see below), but @@ -153,6 +154,7 @@ pub unsafe trait Contiguous: Copy + 'static { /// This is undefined behavior regardless, so it could have been the nasal /// demons at that point anyway ;). #[inline] + #[cfg_attr(feature = "track_caller", track_caller)] fn into_integer(self) -> Self::Int { // Guard against an illegal implementation of Contiguous. Annoyingly we // can't rely on `transmute` to do the size check for us (see diff --git a/src/internal.rs b/src/internal.rs index 06935e6..9baeb4e 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -23,6 +23,7 @@ possibility code branch. #[cfg(not(target_arch = "spirv"))] #[cold] #[inline(never)] +#[cfg_attr(feature = "track_caller", track_caller)] pub(crate) fn something_went_wrong( _src: &str, _err: D, ) -> ! { @@ -75,6 +76,7 @@ pub(crate) unsafe fn bytes_of_mut(t: &mut T) -> &mut [u8] { /// /// This is [`try_from_bytes`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub(crate) unsafe fn from_bytes(s: &[u8]) -> &T { match try_from_bytes(s) { Ok(t) => t, @@ -88,6 +90,7 @@ pub(crate) unsafe fn from_bytes(s: &[u8]) -> &T { /// /// This is [`try_from_bytes_mut`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub(crate) unsafe fn from_bytes_mut(s: &mut [u8]) -> &mut T { match try_from_bytes_mut(s) { Ok(t) => t, @@ -115,6 +118,7 @@ pub(crate) unsafe fn try_pod_read_unaligned( /// ## Panics /// * This is like `try_pod_read_unaligned` but will panic on failure. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub(crate) unsafe fn pod_read_unaligned(bytes: &[u8]) -> T { match try_pod_read_unaligned(bytes) { Ok(t) => t, @@ -127,6 +131,7 @@ pub(crate) unsafe fn pod_read_unaligned(bytes: &[u8]) -> T { /// ## Panics /// * If `align` is not a power of two. This includes when `align` is zero. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub(crate) fn is_aligned_to(ptr: *const (), align: usize) -> bool { #[cfg(feature = "align_offset")] { @@ -180,12 +185,13 @@ pub(crate) unsafe fn try_from_bytes_mut( } } -/// Cast `T` into `U` +/// Cast `A` into `B` /// /// ## Panics /// /// * This is like [`try_cast`](try_cast), but will panic on a size mismatch. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub(crate) unsafe fn cast(a: A) -> B { if size_of::() == size_of::() { unsafe { transmute!(a) } @@ -194,12 +200,13 @@ pub(crate) unsafe fn cast(a: A) -> B { } } -/// Cast `&mut T` into `&mut U`. +/// Cast `&mut A` into `&mut B`. /// /// ## Panics /// /// This is [`try_cast_mut`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub(crate) unsafe fn cast_mut(a: &mut A) -> &mut B { if size_of::() == size_of::() && align_of::() >= align_of::() { // Plz mr compiler, just notice that we can't ever hit Err in this case. @@ -215,12 +222,13 @@ pub(crate) unsafe fn cast_mut(a: &mut A) -> &mut B { } } -/// Cast `&T` into `&U`. +/// Cast `&A` into `&B`. /// /// ## Panics /// /// This is [`try_cast_ref`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub(crate) unsafe fn cast_ref(a: &A) -> &B { if size_of::() == size_of::() && align_of::() >= align_of::() { // Plz mr compiler, just notice that we can't ever hit Err in this case. @@ -242,6 +250,7 @@ pub(crate) unsafe fn cast_ref(a: &A) -> &B { /// /// This is [`try_cast_slice`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub(crate) unsafe fn cast_slice(a: &[A]) -> &[B] { match try_cast_slice(a) { Ok(b) => b, @@ -249,12 +258,13 @@ pub(crate) unsafe fn cast_slice(a: &[A]) -> &[B] { } } -/// Cast `&mut [T]` into `&mut [U]`. +/// Cast `&mut [A]` into `&mut [B]`. /// /// ## Panics /// /// This is [`try_cast_slice_mut`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub(crate) unsafe fn cast_slice_mut(a: &mut [A]) -> &mut [B] { match try_cast_slice_mut(a) { Ok(b) => b, @@ -262,7 +272,7 @@ pub(crate) unsafe fn cast_slice_mut(a: &mut [A]) -> &mut [B] { } } -/// Try to cast `T` into `U`. +/// Try to cast `A` into `B`. /// /// Note that for this particular type of cast, alignment isn't a factor. The /// input value is semantically copied into the function and then returned to a @@ -283,7 +293,7 @@ pub(crate) unsafe fn try_cast( } } -/// Try to convert a `&T` into `&U`. +/// Try to convert a `&A` into `&B`. /// /// ## Failure /// @@ -306,7 +316,7 @@ pub(crate) unsafe fn try_cast_ref( } } -/// Try to convert a `&mut T` into `&mut U`. +/// Try to convert a `&mut A` into `&mut B`. /// /// As [`try_cast_ref`], but `mut`. #[inline] diff --git a/src/lib.rs b/src/lib.rs index ca7c1d6..2d7f270 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,18 +123,20 @@ macro_rules! transmute { ($val:expr) => { ::core::mem::transmute_copy(&::core::mem::ManuallyDrop::new($val)) }; - // This arm is for use in const contexts, where the borrow required to use transmute_copy poses an issue - // since the compiler hedges that the type being borrowed could have interior mutability. - ($srcty:ty; $dstty:ty; $val:expr) => { - { - #[repr(C)] - union Transmute { - src: ::core::mem::ManuallyDrop, - dst: ::core::mem::ManuallyDrop, - } - ::core::mem::ManuallyDrop::into_inner(Transmute::<$srcty, $dstty> { src: ::core::mem::ManuallyDrop::new($val) }.dst) + // This arm is for use in const contexts, where the borrow required to use + // transmute_copy poses an issue since the compiler hedges that the type + // being borrowed could have interior mutability. + ($srcty:ty; $dstty:ty; $val:expr) => {{ + #[repr(C)] + union Transmute { + src: ::core::mem::ManuallyDrop, + dst: ::core::mem::ManuallyDrop, } - } + ::core::mem::ManuallyDrop::into_inner( + Transmute::<$srcty, $dstty> { src: ::core::mem::ManuallyDrop::new($val) } + .dst, + ) + }}; } /// A macro to implement marker traits for various simd types. @@ -210,12 +212,12 @@ pub use bytemuck_derive::{ /// The things that can go wrong when casting between [`Pod`] data forms. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PodCastError { - /// You tried to cast a reference into a reference to a type with a higher alignment - /// requirement but the input reference wasn't aligned. + /// You tried to cast a reference into a reference to a type with a higher + /// alignment requirement but the input reference wasn't aligned. TargetAlignmentGreaterAndInputNotAligned, - /// If the element size of a slice changes, then the output slice changes length - /// accordingly. If the output slice wouldn't be a whole number of elements, - /// then the conversion fails. + /// If the element size of a slice changes, then the output slice changes + /// length accordingly. If the output slice wouldn't be a whole number of + /// elements, then the conversion fails. OutputSliceWouldHaveSlop, /// When casting an individual `T`, `&T`, or `&mut T` value the /// source size and destination size must be an exact match. @@ -262,6 +264,7 @@ pub fn bytes_of_mut(t: &mut T) -> &mut [u8] { /// /// This is like [`try_from_bytes`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn from_bytes(s: &[u8]) -> &T { unsafe { internal::from_bytes(s) } } @@ -272,6 +275,7 @@ pub fn from_bytes(s: &[u8]) -> &T { /// /// This is like [`try_from_bytes_mut`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn from_bytes_mut(s: &mut [u8]) -> &mut T { unsafe { internal::from_bytes_mut(s) } } @@ -298,6 +302,7 @@ pub fn try_pod_read_unaligned( /// ## Panics /// * This is like `try_pod_read_unaligned` but will panic on failure. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn pod_read_unaligned(bytes: &[u8]) -> T { unsafe { internal::pod_read_unaligned(bytes) } } @@ -326,34 +331,37 @@ pub fn try_from_bytes_mut( unsafe { internal::try_from_bytes_mut(s) } } -/// Cast `T` into `U` +/// Cast `A` into `B` /// /// ## Panics /// /// * This is like [`try_cast`], but will panic on a size mismatch. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn cast(a: A) -> B { unsafe { internal::cast(a) } } -/// Cast `&mut T` into `&mut U`. +/// Cast `&mut A` into `&mut B`. /// /// ## Panics /// /// This is [`try_cast_mut`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn cast_mut( a: &mut A, ) -> &mut B { unsafe { internal::cast_mut(a) } } -/// Cast `&T` into `&U`. +/// Cast `&A` into `&B`. /// /// ## Panics /// /// This is [`try_cast_ref`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn cast_ref(a: &A) -> &B { unsafe { internal::cast_ref(a) } } @@ -364,16 +372,18 @@ pub fn cast_ref(a: &A) -> &B { /// /// This is [`try_cast_slice`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn cast_slice(a: &[A]) -> &[B] { unsafe { internal::cast_slice(a) } } -/// Cast `&mut [T]` into `&mut [U]`. +/// Cast `&mut [A]` into `&mut [B]`. /// /// ## Panics /// /// This is [`try_cast_slice_mut`] but will panic on error. #[inline] +#[cfg_attr(feature = "track_caller", track_caller)] pub fn cast_slice_mut< A: NoUninit + AnyBitPattern, B: NoUninit + AnyBitPattern, @@ -404,7 +414,7 @@ pub fn pod_align_to_mut< unsafe { vals.align_to_mut::() } } -/// Try to cast `T` into `U`. +/// Try to cast `A` into `B`. /// /// Note that for this particular type of cast, alignment isn't a factor. The /// input value is semantically copied into the function and then returned to a @@ -421,7 +431,7 @@ pub fn try_cast( unsafe { internal::try_cast(a) } } -/// Try to convert a `&T` into `&U`. +/// Try to convert a `&A` into `&B`. /// /// ## Failure /// @@ -434,7 +444,7 @@ pub fn try_cast_ref( unsafe { internal::try_cast_ref(a) } } -/// Try to convert a `&mut T` into `&mut U`. +/// Try to convert a `&mut A` into `&mut B`. /// /// As [`try_cast_ref`], but `mut`. #[inline]